diff --git a/conda_lock/_vendor/cleo.LICENSE b/conda_lock/_vendor/cleo.LICENSE deleted file mode 100644 index 3f0aed7fa..000000000 --- a/conda_lock/_vendor/cleo.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2013 Sébastien Eustace - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/conda_lock/_vendor/cleo/io/io_mixin.py b/conda_lock/_vendor/cleo/io/io_mixin.py deleted file mode 100644 index 6bca5406c..000000000 --- a/conda_lock/_vendor/cleo/io/io_mixin.py +++ /dev/null @@ -1,112 +0,0 @@ -from clikit.ui.components import ChoiceQuestion -from clikit.ui.components import ConfirmationQuestion -from clikit.ui.components import ProgressBar -from clikit.ui.components import Question - - -class IOMixin(object): - """ - Helpers for IO classes - """ - - def __init__(self, *args, **kwargs): - super(IOMixin, self).__init__(*args, **kwargs) - - self._last_message = "" - self._last_message_err = "" - - def progress_bar(self, max=0): # type: (int) -> ProgressBar - """ - Create a new progress bar - """ - return ProgressBar(self, max) - - def ask(self, question, default=None): - question = Question(question, default) - - return self.ask_question(question) - - def ask_hidden(self, question): - question = Question(question) - question.hide() - - return self.ask_question(question) - - def confirm(self, question, default=True, true_answer_regex="(?i)^y"): - return self.ask_question( - ConfirmationQuestion(question, default, true_answer_regex) - ) - - def choice(self, question, choices, default=None): - if default is not None: - default = choices[default] - - return self.ask_question(ChoiceQuestion(question, choices, default)) - - def ask_question(self, question): - """ - Asks a question. - """ - answer = question.ask(self) - - return answer - - def write(self, string, flags=0): - super(IOMixin, self).write(string, flags) - - self._last_message = string - - def error(self, string, flags=0): - super(IOMixin, self).error(string, flags) - - self._last_message = string - - def write_line(self, string, flags=0): - super(IOMixin, self).write_line(string, flags) - - self._last_message = string - - def error_line(self, string, flags=0): - super(IOMixin, self).error_line(string, flags) - - self._last_message = string - - def overwrite(self, message, size=None): - self._do_overwrite(message, size) - - def overwrite_error(self, message, size=None): - self._do_overwrite(message, size, True) - - def _do_overwrite(self, message, size=None, stderr=False): - output = self.output - if stderr: - output = self.error_output - - # since overwrite is supposed to overwrite last message... - if size is None: - # removing possible formatting of lastMessage with strip_tags - if stderr: - last_message = self._last_message_err - else: - last_message = self._last_message - - size = len(output.remove_format(last_message)) - - # ...let's fill its length with backspaces - output.write("\x08" * size) - - # write the new message - output.write(message) - - fill = size - len(output.remove_format(message)) - - if fill > 0: - # whitespace whatever has left - output.write(" " * fill) - # move the cursor back - output.write("\x08" * fill) - - if stderr: - self._last_message_err = message - else: - self._last_message = message diff --git a/conda_lock/_vendor/conda/LICENSE.txt b/conda_lock/_vendor/conda/LICENSE.txt index a51531d9e..eeb91b202 100644 --- a/conda_lock/_vendor/conda/LICENSE.txt +++ b/conda_lock/_vendor/conda/LICENSE.txt @@ -1,7 +1,28 @@ -Copyright (c) 2012 Santiago Lezica +Copyright (c) 2013 Matthew Rocklin -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +All rights reserved. -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + a. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + c. Neither the name of toolz nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. diff --git a/conda_lock/_vendor/poetry.LICENSE b/conda_lock/_vendor/poetry.LICENSE deleted file mode 100644 index 44cf2b30e..000000000 --- a/conda_lock/_vendor/poetry.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2018 Sébastien Eustace - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/conda_lock/_vendor/poetry.pyi b/conda_lock/_vendor/poetry.pyi deleted file mode 100644 index b994d366d..000000000 --- a/conda_lock/_vendor/poetry.pyi +++ /dev/null @@ -1 +0,0 @@ -from poetry import * \ No newline at end of file diff --git a/conda_lock/_vendor/poetry/LICENSE b/conda_lock/_vendor/poetry/LICENSE index 44cf2b30e..81a8e1e47 100644 --- a/conda_lock/_vendor/poetry/LICENSE +++ b/conda_lock/_vendor/poetry/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 Sébastien Eustace +Copyright (c) 2018-present Sébastien Eustace Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/conda_lock/_vendor/poetry/__init__.py b/conda_lock/_vendor/poetry/__init__.py deleted file mode 100644 index 26cfe4052..000000000 --- a/conda_lock/_vendor/poetry/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from pkgutil import extend_path - - -__path__ = extend_path(__path__, __name__) diff --git a/conda_lock/_vendor/poetry/__main__.py b/conda_lock/_vendor/poetry/__main__.py index b280ed84e..dbbc659dc 100644 --- a/conda_lock/_vendor/poetry/__main__.py +++ b/conda_lock/_vendor/poetry/__main__.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import sys if __name__ == "__main__": - from .console import main + from conda_lock._vendor.poetry.console.application import main sys.exit(main()) diff --git a/conda_lock/_vendor/poetry/__version__.py b/conda_lock/_vendor/poetry/__version__.py index 316ae3d0c..0de46a49b 100644 --- a/conda_lock/_vendor/poetry/__version__.py +++ b/conda_lock/_vendor/poetry/__version__.py @@ -1 +1,16 @@ -__version__ = "1.1.15" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.utils._compat import metadata + + +if TYPE_CHECKING: + from collections.abc import Callable + + +# The metadata.version that we import for Python 3.7 is untyped, work around +# that. +version: Callable[[str], str] = metadata.version + +__version__ = version("poetry") diff --git a/conda_lock/_vendor/poetry/config/config.py b/conda_lock/_vendor/poetry/config/config.py index 52f5b2d3b..605f2e0db 100644 --- a/conda_lock/_vendor/poetry/config/config.py +++ b/conda_lock/_vendor/poetry/config/config.py @@ -1,94 +1,181 @@ -from __future__ import absolute_import +from __future__ import annotations +import dataclasses +import logging import os import re from copy import deepcopy +from pathlib import Path +from typing import TYPE_CHECKING from typing import Any -from typing import Callable -from typing import Dict -from typing import Optional -from conda_lock._vendor.poetry.locations import CACHE_DIR -from conda_lock._vendor.poetry.utils._compat import Path -from conda_lock._vendor.poetry.utils._compat import basestring +from packaging.utils import canonicalize_name +from conda_lock._vendor.poetry.core.toml import TOMLFile -from .config_source import ConfigSource -from .dict_config_source import DictConfigSource +from conda_lock._vendor.poetry.config.dict_config_source import DictConfigSource +from conda_lock._vendor.poetry.config.file_config_source import FileConfigSource +from conda_lock._vendor.poetry.locations import CONFIG_DIR +from conda_lock._vendor.poetry.locations import DEFAULT_CACHE_DIR -_NOT_SET = object() +if TYPE_CHECKING: + from collections.abc import Callable + from conda_lock._vendor.poetry.config.config_source import ConfigSource -def boolean_validator(val): + +def boolean_validator(val: str) -> bool: return val in {"true", "false", "1", "0"} -def boolean_normalizer(val): +def boolean_normalizer(val: str) -> bool: return val in ["true", "1"] -class Config(object): +def int_normalizer(val: str) -> int: + return int(val) + + +@dataclasses.dataclass +class PackageFilterPolicy: + policy: dataclasses.InitVar[str | list[str] | None] + packages: list[str] = dataclasses.field(init=False) + + def __post_init__(self, policy: str | list[str] | None) -> None: + if not policy: + policy = [] + elif isinstance(policy, str): + policy = self.normalize(policy) + self.packages = policy + + def allows(self, package_name: str) -> bool: + if ":all:" in self.packages: + return False + + return ( + not self.packages + or ":none:" in self.packages + or canonicalize_name(package_name) not in self.packages + ) + + @classmethod + def is_reserved(cls, name: str) -> bool: + return bool(re.match(r":(all|none):", name)) + + @classmethod + def normalize(cls, policy: str) -> list[str]: + if boolean_validator(policy): + if boolean_normalizer(policy): + return [":all:"] + else: + return [":none:"] + + return list( + { + name.strip() if cls.is_reserved(name) else canonicalize_name(name) + for name in policy.strip().split(",") + if name + } + ) + + @classmethod + def validator(cls, policy: str) -> bool: + if boolean_validator(policy): + return True + + names = policy.strip().split(",") - default_config = { - "cache-dir": str(CACHE_DIR), + for name in names: + if ( + not name + or (cls.is_reserved(name) and len(names) == 1) + or re.match(r"^[a-zA-Z\d_-]+$", name) + ): + continue + return False + + return True + + +logger = logging.getLogger(__name__) + + +_default_config: Config | None = None + + +class Config: + default_config: dict[str, Any] = { + "cache-dir": str(DEFAULT_CACHE_DIR), "virtualenvs": { "create": True, "in-project": None, "path": os.path.join("{cache-dir}", "virtualenvs"), + "options": { + "always-copy": False, + "system-site-packages": False, + # we default to False here in order to prevent development environment + # breakages for IDEs etc. as when working in these environments + # assumptions are often made about virtual environments having pip and + # setuptools. + "no-pip": False, + "no-setuptools": False, + }, + "prefer-active-python": False, + "prompt": "{project_name}-py{python_version}", }, - "experimental": {"new-installer": True}, - "installer": {"parallel": True}, + "experimental": {"new-installer": True, "system-git-client": False}, + "installer": {"parallel": True, "max-workers": None, "no-binary": None}, } def __init__( - self, use_environment=True, base_dir=None - ): # type: (bool, Optional[Path]) -> None + self, use_environment: bool = True, base_dir: Path | None = None + ) -> None: self._config = deepcopy(self.default_config) self._use_environment = use_environment self._base_dir = base_dir - self._config_source = DictConfigSource() - self._auth_config_source = DictConfigSource() - - @property - def name(self): - return str(self._file.path) + self._config_source: ConfigSource = DictConfigSource() + self._auth_config_source: ConfigSource = DictConfigSource() @property - def config(self): + def config(self) -> dict[str, Any]: return self._config @property - def config_source(self): # type: () -> ConfigSource + def config_source(self) -> ConfigSource: return self._config_source @property - def auth_config_source(self): # type: () -> ConfigSource + def auth_config_source(self) -> ConfigSource: return self._auth_config_source - def set_config_source(self, config_source): # type: (ConfigSource) -> Config + def set_config_source(self, config_source: ConfigSource) -> Config: self._config_source = config_source return self - def set_auth_config_source(self, config_source): # type: (ConfigSource) -> Config + def set_auth_config_source(self, config_source: ConfigSource) -> Config: self._auth_config_source = config_source return self - def merge(self, config): # type: (Dict[str, Any]) -> None + def merge(self, config: dict[str, Any]) -> None: from conda_lock._vendor.poetry.utils.helpers import merge_dicts merge_dicts(self._config, config) - def all(self): # type: () -> Dict[str, Any] - def _all(config, parent_key=""): + def all(self) -> dict[str, Any]: + def _all(config: dict[str, Any], parent_key: str = "") -> dict[str, Any]: all_ = {} for key in config: value = self.get(parent_key + key) if isinstance(value, dict): - all_[key] = _all(config[key], parent_key=key + ".") + if parent_key != "": + current_parent = parent_key + key + "." + else: + current_parent = key + "." + all_[key] = _all(config[key], parent_key=current_parent) continue all_[key] = value @@ -97,10 +184,35 @@ def _all(config, parent_key=""): return _all(self.config) - def raw(self): # type: () -> Dict[str, Any] + def raw(self) -> dict[str, Any]: return self._config - def get(self, setting_name, default=None): # type: (str, Any) -> Any + @staticmethod + def _get_environment_repositories() -> dict[str, dict[str, str]]: + repositories = {} + pattern = re.compile(r"POETRY_REPOSITORIES_(?P[A-Z_]+)_URL") + + for env_key in os.environ.keys(): + match = pattern.match(env_key) + if match: + repositories[match.group("name").lower().replace("_", "-")] = { + "url": os.environ[env_key] + } + + return repositories + + @property + 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. """ @@ -109,12 +221,16 @@ def get(self, setting_name, default=None): # type: (str, Any) -> Any # Looking in the environment if the setting # is set via a POETRY_* environment variable if self._use_environment: - env = "POETRY_{}".format( - "_".join(k.upper().replace("-", "_") for k in keys) - ) - value = os.getenv(env, _NOT_SET) - if value is not _NOT_SET: - return self.process(self._get_normalizer(setting_name)(value)) + if setting_name == "repositories": + # repositories setting is special for now + repositories = self._get_environment_repositories() + if repositories: + return repositories + + env = "POETRY_" + "_".join(k.upper().replace("-", "_") for k in keys) + env_value = os.getenv(env) + if env_value is not None: + return self.process(self._get_normalizer(setting_name)(env_value)) value = self._config for key in keys: @@ -125,27 +241,32 @@ def get(self, setting_name, default=None): # type: (str, Any) -> Any return self.process(value) - def process(self, value): # type: (Any) -> Any - if not isinstance(value, basestring): + def process(self, value: Any) -> Any: + if not isinstance(value, str): return value - return re.sub(r"{(.+?)}", lambda m: self.get(m.group(1)), value) + def resolve_from_config(match: re.Match[str]) -> Any: + key = match.group(1) + config_value = self.get(key) + if config_value: + return config_value - def _get_validator(self, name): # type: (str) -> Callable - if name in { - "virtualenvs.create", - "virtualenvs.in-project", - "installer.parallel", - }: - return boolean_validator + # The key doesn't exist in the config but might be resolved later, + # so we keep it as a format variable. + return f"{{{key}}}" - if name == "virtualenvs.path": - return str + return re.sub(r"{(.+?)}", resolve_from_config, value) - def _get_normalizer(self, name): # type: (str) -> Callable + @staticmethod + def _get_normalizer(name: str) -> Callable[[str], Any]: if name in { "virtualenvs.create", "virtualenvs.in-project", + "virtualenvs.options.always-copy", + "virtualenvs.options.system-site-packages", + "virtualenvs.options.prefer-active-python", + "experimental.new-installer", + "experimental.system-git-client", "installer.parallel", }: return boolean_normalizer @@ -153,4 +274,35 @@ def _get_normalizer(self, name): # type: (str) -> Callable if name == "virtualenvs.path": return lambda val: str(Path(val)) + if name == "installer.max-workers": + return int_normalizer + + if name == "installer.no-binary": + return PackageFilterPolicy.normalize + return lambda val: val + + @classmethod + def create(cls, reload: bool = False) -> Config: + global _default_config + + if _default_config is None or reload: + _default_config = cls() + + # Load global config + config_file = TOMLFile(CONFIG_DIR / "config.toml") + if config_file.exists(): + logger.debug("Loading configuration file %s", config_file.path) + _default_config.merge(config_file.read()) + + _default_config.set_config_source(FileConfigSource(config_file)) + + # Load global auth config + auth_config_file = TOMLFile(CONFIG_DIR / "auth.toml") + if auth_config_file.exists(): + logger.debug("Loading configuration file %s", auth_config_file.path) + _default_config.merge(auth_config_file.read()) + + _default_config.set_auth_config_source(FileConfigSource(auth_config_file)) + + return _default_config diff --git a/conda_lock/_vendor/poetry/config/config_source.py b/conda_lock/_vendor/poetry/config/config_source.py index 63a4ad6b6..ed97fa917 100644 --- a/conda_lock/_vendor/poetry/config/config_source.py +++ b/conda_lock/_vendor/poetry/config/config_source.py @@ -1,9 +1,11 @@ +from __future__ import annotations + from typing import Any -class ConfigSource(object): - def add_property(self, key, value): # type: (str, Any) -> None +class ConfigSource: + def add_property(self, key: str, value: Any) -> None: raise NotImplementedError() - def remove_property(self, key): # type: (str) -> None + def remove_property(self, key: str) -> None: raise NotImplementedError() diff --git a/conda_lock/_vendor/poetry/config/dict_config_source.py b/conda_lock/_vendor/poetry/config/dict_config_source.py index aaa6ee3b9..4ebc24d5e 100644 --- a/conda_lock/_vendor/poetry/config/dict_config_source.py +++ b/conda_lock/_vendor/poetry/config/dict_config_source.py @@ -1,18 +1,19 @@ +from __future__ import annotations + from typing import Any -from typing import Dict -from .config_source import ConfigSource +from conda_lock._vendor.poetry.config.config_source import ConfigSource class DictConfigSource(ConfigSource): - def __init__(self): # type: () -> None - self._config = {} + def __init__(self) -> None: + self._config: dict[str, Any] = {} @property - def config(self): # type: () -> Dict[str, Any] + def config(self) -> dict[str, Any]: return self._config - def add_property(self, key, value): # type: (str, Any) -> None + def add_property(self, key: str, value: Any) -> None: keys = key.split(".") config = self._config @@ -26,7 +27,7 @@ def add_property(self, key, value): # type: (str, Any) -> None config = config[key] - def remove_property(self, key): # type: (str) -> None + def remove_property(self, key: str) -> None: keys = key.split(".") config = self._config diff --git a/conda_lock/_vendor/poetry/config/file_config_source.py b/conda_lock/_vendor/poetry/config/file_config_source.py index cec20aea9..9acacea7e 100644 --- a/conda_lock/_vendor/poetry/config/file_config_source.py +++ b/conda_lock/_vendor/poetry/config/file_config_source.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import contextmanager from typing import TYPE_CHECKING from typing import Any @@ -5,28 +7,32 @@ from tomlkit import document from tomlkit import table -from .config_source import ConfigSource +from conda_lock._vendor.poetry.config.config_source import ConfigSource if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.toml.file import TOMLFile # noqa + from collections.abc import Iterator + + from conda_lock._vendor.poetry.core.toml.file import TOMLFile + from tomlkit.toml_document import TOMLDocument class FileConfigSource(ConfigSource): - def __init__(self, file, auth_config=False): # type: ("TOMLFile", bool) -> None + def __init__(self, file: TOMLFile, auth_config: bool = False) -> None: self._file = file self._auth_config = auth_config @property - def name(self): # type: () -> str + def name(self) -> str: return str(self._file.path) @property - def file(self): # type: () -> "TOMLFile" + def file(self) -> TOMLFile: return self._file - def add_property(self, key, value): # type: (str, Any) -> None - with self.secure() as config: + def add_property(self, key: str, value: Any) -> None: + with self.secure() as toml: + config: dict[str, Any] = toml keys = key.split(".") for i, key in enumerate(keys): @@ -39,8 +45,9 @@ def add_property(self, key, value): # type: (str, Any) -> None config = config[key] - def remove_property(self, key): # type: (str) -> None - with self.secure() as config: + def remove_property(self, key: str) -> None: + with self.secure() as toml: + config: dict[str, Any] = toml keys = key.split(".") current_config = config @@ -56,7 +63,7 @@ def remove_property(self, key): # type: (str) -> None current_config = current_config[key] @contextmanager - def secure(self): + def secure(self) -> Iterator[TOMLDocument]: if self.file.exists(): initial_config = self.file.read() config = self.file.read() diff --git a/conda_lock/_vendor/poetry/config/source.py b/conda_lock/_vendor/poetry/config/source.py new file mode 100644 index 000000000..f3af0c589 --- /dev/null +++ b/conda_lock/_vendor/poetry/config/source.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +import dataclasses + + +@dataclasses.dataclass(order=True, eq=True) +class Source: + name: str + url: str + default: bool = dataclasses.field(default=False) + secondary: bool = dataclasses.field(default=False) + + def to_dict(self) -> dict[str, str | bool]: + return dataclasses.asdict(self) diff --git a/conda_lock/_vendor/poetry/core/__init__.py b/conda_lock/_vendor/poetry/core/__init__.py index f7d95ecea..a061a5506 100644 --- a/conda_lock/_vendor/poetry/core/__init__.py +++ b/conda_lock/_vendor/poetry/core/__init__.py @@ -1,13 +1,13 @@ +from __future__ import annotations + import sys +from pathlib import Path -try: - from pathlib import Path -except ImportError: - # noinspection PyUnresolvedReferences - from pathlib2 import Path -__version__ = "1.0.8" +# this cannot presently be replaced with importlib.metadata.version as when building +# itself, poetry-core is not available as an installed distribution. +__version__ = "1.4.0" __vendor_site__ = (Path(__file__).parent / "_vendor").as_posix() diff --git a/conda_lock/_vendor/poetry/core/_vendor/_pyrsistent_version.py b/conda_lock/_vendor/poetry/core/_vendor/_pyrsistent_version.py index 9513287c9..5daae67f7 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/_pyrsistent_version.py +++ b/conda_lock/_vendor/poetry/core/_vendor/_pyrsistent_version.py @@ -1 +1 @@ -__version__ = '0.16.1' +__version__ = '0.19.2' diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/attr/__init__.py index bf329cad5..386305d62 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/__init__.py @@ -1,10 +1,12 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT + import sys from functools import partial from . import converters, exceptions, filters, setters, validators +from ._cmp import cmp_using from ._config import get_run_validators, set_run_validators from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types from ._make import ( @@ -21,7 +23,7 @@ from ._version_info import VersionInfo -__version__ = "20.3.0" +__version__ = "22.1.0" __version_info__ = VersionInfo._from_version_string(__version__) __title__ = "attrs" @@ -52,6 +54,7 @@ "attrib", "attributes", "attrs", + "cmp_using", "converters", "evolve", "exceptions", @@ -71,6 +74,6 @@ ] if sys.version_info[:2] >= (3, 6): - from ._next_gen import define, field, frozen, mutable + from ._next_gen import define, field, frozen, mutable # noqa: F401 - __all__.extend((define, field, frozen, mutable)) + __all__.extend(("define", "field", "frozen", "mutable")) diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_cmp.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_cmp.py new file mode 100644 index 000000000..81b99e4c3 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_cmp.py @@ -0,0 +1,155 @@ +# SPDX-License-Identifier: MIT + + +import functools +import types + +from ._make import _make_ne + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and + ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if + at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + :param Optional[callable] eq: `callable` used to evaluate equality + of two objects. + :param Optional[callable] lt: `callable` used to evaluate whether + one object is less than another object. + :param Optional[callable] le: `callable` used to evaluate whether + one object is less than or equal to another object. + :param Optional[callable] gt: `callable` used to evaluate whether + one object is greater than another object. + :param Optional[callable] ge: `callable` used to evaluate whether + one object is greater than or equal to another object. + + :param bool require_same_type: When `True`, equality and ordering methods + will return `NotImplemented` if objects are not of the same type. + + :param Optional[str] class_name: Name of class. Defaults to 'Comparable'. + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = _make_ne() + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = types.new_class( + class_name, (object,), {}, lambda ns: ns.update(body) + ) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + raise ValueError( + "eq must be define is order to complete ordering from " + "lt, le, gt, ge." + ) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = "__%s__" % (name,) + method.__doc__ = "Return a %s b. Computed by attrs." % ( + _operation_names[name], + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + for func in self._requirements: + if not func(self, other): + return False + return True + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_compat.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_compat.py index b0ead6e1c..582649325 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/_compat.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_compat.py @@ -1,16 +1,23 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT + +import inspect import platform import sys +import threading import types import warnings +from collections.abc import Mapping, Sequence # noqa + -PY2 = sys.version_info[0] == 2 PYPY = platform.python_implementation() == "PyPy" +PY36 = sys.version_info[:2] >= (3, 6) +HAS_F_STRINGS = PY36 +PY310 = sys.version_info[:2] >= (3, 10) -if PYPY or sys.version_info[:2] >= (3, 6): +if PYPY or PY36: ordered_dict = dict else: from collections import OrderedDict @@ -18,112 +25,54 @@ ordered_dict = OrderedDict -if PY2: - from collections import Mapping, Sequence - - from UserDict import IterableUserDict - - # We 'bundle' isclass instead of using inspect as importing inspect is - # fairly expensive (order of 10-15 ms for a modern machine in 2016) - def isclass(klass): - return isinstance(klass, (type, types.ClassType)) - - # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. - TYPE = "type" - - def iteritems(d): - return d.iteritems() - - # Python 2 is bereft of a read-only dict proxy, so we make one! - class ReadOnlyDict(IterableUserDict): - """ - Best-effort read-only dict wrapper. - """ - - def __setitem__(self, key, val): - # We gently pretend we're a Python 3 mappingproxy. - raise TypeError( - "'mappingproxy' object does not support item assignment" - ) - - def update(self, _): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'update'" - ) - - def __delitem__(self, _): - # We gently pretend we're a Python 3 mappingproxy. - raise TypeError( - "'mappingproxy' object does not support item deletion" - ) - - def clear(self): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'clear'" - ) - - def pop(self, key, default=None): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'pop'" - ) +def just_warn(*args, **kw): + warnings.warn( + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " + "__class__ will not work with slotted classes.", + RuntimeWarning, + stacklevel=2, + ) - def popitem(self): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'popitem'" - ) - def setdefault(self, key, default=None): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'setdefault'" - ) +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ - def __repr__(self): - # Override to be identical to the Python 3 version. - return "mappingproxy(" + repr(self.data) + ")" + __slots__ = ["sig"] - def metadata_proxy(d): - res = ReadOnlyDict() - res.data.update(d) # We blocked update, so we have to do it like this. - return res + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None - def just_warn(*args, **kw): # pragma: no cover + def get_first_param_type(self): """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. + Return the type annotation of the first argument if it's not empty. """ + if not self.sig: + return None + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation -else: # Python 3 and later. - from collections.abc import Mapping, Sequence # noqa + return None - def just_warn(*args, **kw): + def get_return_type(self): """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. + Return the return type if it's not empty. """ - warnings.warn( - "Running interpreter doesn't sufficiently support code object " - "introspection. Some features like bare super() or accessing " - "__class__ will not work with slotted classes.", - RuntimeWarning, - stacklevel=2, - ) - - def isclass(klass): - return isinstance(klass, type) - - TYPE = "class" - - def iteritems(d): - return d.items() + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation - def metadata_proxy(d): - return types.MappingProxyType(dict(d)) + return None def make_set_closure_cell(): @@ -155,26 +104,20 @@ def force_x_to_be_a_cell(): # pragma: no cover try: # Extract the code object and make sure our assumptions about # the closure behavior are correct. - if PY2: - co = set_first_cellvar_to.func_code - else: - co = set_first_cellvar_to.__code__ + co = set_first_cellvar_to.__code__ if co.co_cellvars != ("x",) or co.co_freevars != (): raise AssertionError # pragma: no cover # Convert this code object to a code object that sets the # function's first _freevar_ (not cellvar) to the argument. if sys.version_info >= (3, 8): - # CPython 3.8+ has an incompatible CodeType signature - # (added a posonlyargcount argument) but also added - # CodeType.replace() to do this without counting parameters. - set_first_freevar_code = co.replace( - co_cellvars=co.co_freevars, co_freevars=co.co_cellvars - ) + + def set_closure_cell(cell, value): + cell.cell_contents = value + else: args = [co.co_argcount] - if not PY2: - args.append(co.co_kwonlyargcount) + args.append(co.co_kwonlyargcount) args.extend( [ co.co_nlocals, @@ -195,15 +138,15 @@ def force_x_to_be_a_cell(): # pragma: no cover ) set_first_freevar_code = types.CodeType(*args) - def set_closure_cell(cell, value): - # Create a function using the set_first_freevar_code, - # whose first closure cell is `cell`. Calling it will - # change the value of that cell. - setter = types.FunctionType( - set_first_freevar_code, {}, "setter", (), (cell,) - ) - # And call it to set the cell. - setter(value) + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) # Make sure it works on this interpreter: def make_func_with_cell(): @@ -214,10 +157,7 @@ def func(): return func - if PY2: - cell = make_func_with_cell().func_closure[0] - else: - cell = make_func_with_cell().__closure__[0] + cell = make_func_with_cell().__closure__[0] set_closure_cell(cell, 100) if cell.cell_contents != 100: raise AssertionError # pragma: no cover @@ -229,3 +169,17 @@ def func(): set_closure_cell = make_set_closure_cell() + +# Thread-local global to track attrs instances which are already being repr'd. +# This is needed because there is no other (thread-safe) way to pass info +# about the instances that are already being repr'd through the call stack +# in order to ensure we don't perform infinite recursion. +# +# For instance, if an instance contains a dict which contains that instance, +# we need to know that we're already repr'ing the outside instance from within +# the dict's repr() call. +# +# This lives here rather than in _make.py so that the functions in _make.py +# don't have a direct reference to the thread-local in their globals dict. +# If they have such a reference, it breaks cloudpickle. +repr_context = threading.local() diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_config.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_config.py index 8ec920962..96d420077 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/_config.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_config.py @@ -1,4 +1,4 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT __all__ = ["set_run_validators", "get_run_validators"] @@ -9,6 +9,10 @@ def set_run_validators(run): """ Set whether or not validators are run. By default, they are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` + instead. """ if not isinstance(run, bool): raise TypeError("'run' must be bool.") @@ -19,5 +23,9 @@ def set_run_validators(run): def get_run_validators(): """ Return whether or not validators are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` + instead. """ return _run_validators diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_funcs.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_funcs.py index e6c930cbd..a982d7cb5 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/_funcs.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_funcs.py @@ -1,8 +1,8 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT + import copy -from ._compat import iteritems from ._make import NOTHING, _obj_setattr, fields from .exceptions import AttrsAttributeNotFoundError @@ -25,7 +25,7 @@ def asdict( ``attrs``-decorated. :param callable filter: A callable whose return code determines whether an attribute or element is included (``True``) or dropped (``False``). Is - called with the `attr.Attribute` as the first argument and the + called with the `attrs.Attribute` as the first argument and the value as the second argument. :param callable dict_factory: A callable to produce dictionaries from. For example, to produce ordered dictionaries instead of normal Python @@ -46,6 +46,8 @@ def asdict( .. versionadded:: 16.0.0 *dict_factory* .. versionadded:: 16.1.0 *retain_collection_types* .. versionadded:: 20.3.0 *value_serializer* + .. versionadded:: 21.3.0 If a dict has a collection for a key, it is + serialized as a tuple. """ attrs = fields(inst.__class__) rv = dict_factory() @@ -61,11 +63,11 @@ def asdict( if has(v.__class__): rv[a.name] = asdict( v, - True, - filter, - dict_factory, - retain_collection_types, - value_serializer, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ) elif isinstance(v, (tuple, list, set, frozenset)): cf = v.__class__ if retain_collection_types is True else list @@ -73,10 +75,11 @@ def asdict( [ _asdict_anything( i, - filter, - dict_factory, - retain_collection_types, - value_serializer, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ) for i in v ] @@ -87,20 +90,22 @@ def asdict( ( _asdict_anything( kk, - filter, - df, - retain_collection_types, - value_serializer, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ), _asdict_anything( vv, - filter, - df, - retain_collection_types, - value_serializer, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ), ) - for kk, vv in iteritems(v) + for kk, vv in v.items() ) else: rv[a.name] = v @@ -111,6 +116,7 @@ def asdict( def _asdict_anything( val, + is_key, filter, dict_factory, retain_collection_types, @@ -123,22 +129,29 @@ def _asdict_anything( # Attrs class. rv = asdict( val, - True, - filter, - dict_factory, - retain_collection_types, - value_serializer, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ) elif isinstance(val, (tuple, list, set, frozenset)): - cf = val.__class__ if retain_collection_types is True else list + if retain_collection_types is True: + cf = val.__class__ + elif is_key: + cf = tuple + else: + cf = list + rv = cf( [ _asdict_anything( i, - filter, - dict_factory, - retain_collection_types, - value_serializer, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ) for i in val ] @@ -148,13 +161,23 @@ def _asdict_anything( rv = df( ( _asdict_anything( - kk, filter, df, retain_collection_types, value_serializer + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ), _asdict_anything( - vv, filter, df, retain_collection_types, value_serializer + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, ), ) - for kk, vv in iteritems(val) + for kk, vv in val.items() ) else: rv = val @@ -181,7 +204,7 @@ def astuple( ``attrs``-decorated. :param callable filter: A callable whose return code determines whether an attribute or element is included (``True``) or dropped (``False``). Is - called with the `attr.Attribute` as the first argument and the + called with the `attrs.Attribute` as the first argument and the value as the second argument. :param callable tuple_factory: A callable to produce tuples from. For example, to produce lists instead of tuples. @@ -253,7 +276,7 @@ def astuple( if has(vv.__class__) else vv, ) - for kk, vv in iteritems(v) + for kk, vv in v.items() ) ) else: @@ -291,7 +314,9 @@ def assoc(inst, **changes): class. .. deprecated:: 17.1.0 - Use `evolve` instead. + Use `attrs.evolve` instead if you can. + This function will not be removed du to the slightly different approach + compared to `attrs.evolve`. """ import warnings @@ -302,7 +327,7 @@ def assoc(inst, **changes): ) new = copy.copy(inst) attrs = fields(inst.__class__) - for k, v in iteritems(changes): + for k, v in changes.items(): a = getattr(attrs, k, NOTHING) if a is NOTHING: raise AttrsAttributeNotFoundError( @@ -343,7 +368,7 @@ def evolve(inst, **changes): return cls(**changes) -def resolve_types(cls, globalns=None, localns=None): +def resolve_types(cls, globalns=None, localns=None, attribs=None): """ Resolve any strings and forward annotations in type annotations. @@ -360,31 +385,36 @@ def resolve_types(cls, globalns=None, localns=None): :param type cls: Class to resolve. :param Optional[dict] globalns: Dictionary containing global variables. :param Optional[dict] localns: Dictionary containing local variables. + :param Optional[list] attribs: List of attribs for the given class. + This is necessary when calling from inside a ``field_transformer`` + since *cls* is not an ``attrs`` class yet. :raise TypeError: If *cls* is not a class. :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` - class. + class and you didn't pass any attribs. :raise NameError: If types cannot be resolved because of missing variables. :returns: *cls* so you can use this function also as a class decorator. - Please note that you have to apply it **after** `attr.s`. That means - the decorator has to come in the line **before** `attr.s`. + Please note that you have to apply it **after** `attrs.define`. That + means the decorator has to come in the line **before** `attrs.define`. .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + """ - try: - # Since calling get_type_hints is expensive we cache whether we've - # done it already. - cls.__attrs_types_resolved__ - except AttributeError: + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + if getattr(cls, "__attrs_types_resolved__", None) != cls: import typing hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) - for field in fields(cls): + for field in fields(cls) if attribs is None else attribs: if field.name in hints: # Since fields have been frozen we must work around it. _obj_setattr(field, "type", hints[field.name]) - cls.__attrs_types_resolved__ = True + # We store the class we resolved so that subclasses know they haven't + # been resolved. + cls.__attrs_types_resolved__ = cls # Return the class so you can use it as a decorator too. return cls diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_make.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_make.py index 49484f935..4d1afe3fc 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/_make.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_make.py @@ -1,21 +1,21 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT import copy import linecache import sys -import threading -import uuid -import warnings +import types +import typing from operator import itemgetter -from . import _config, setters +# We need to import _compat itself in addition to the _compat members to avoid +# having the thread-local in the globals here. +from . import _compat, _config, setters from ._compat import ( - PY2, + HAS_F_STRINGS, + PY310, PYPY, - isclass, - iteritems, - metadata_proxy, + _AnnotationExtractor, ordered_dict, set_closure_cell, ) @@ -23,7 +23,6 @@ DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError, - PythonTooOldError, UnannotatedAttributeError, ) @@ -35,35 +34,47 @@ _tuple_property_pat = ( " {attr_name} = _attrs_property(_attrs_itemgetter({index}))" ) -_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar") +_classvar_prefixes = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) # we don't use a double-underscore prefix because that triggers # name mangling when trying to create a slot for the field # (when slots=True) _hash_cache_field = "_attrs_cached_hash" -_empty_metadata_singleton = metadata_proxy({}) +_empty_metadata_singleton = types.MappingProxyType({}) # Unique object for unequivocal getattr() defaults. _sentinel = object() +_ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) + -class _Nothing(object): +class _Nothing: """ Sentinel class to indicate the lack of a value when ``None`` is ambiguous. ``_Nothing`` is a singleton. There is only ever one of it. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. """ _singleton = None def __new__(cls): if _Nothing._singleton is None: - _Nothing._singleton = super(_Nothing, cls).__new__(cls) + _Nothing._singleton = super().__new__(cls) return _Nothing._singleton def __repr__(self): return "NOTHING" + def __bool__(self): + return False + NOTHING = _Nothing() """ @@ -83,17 +94,8 @@ class _CacheHashWrapper(int): See GH #613 for more details. """ - if PY2: - # For some reason `type(None)` isn't callable in Python 2, but we don't - # actually need a constructor for None objects, we just need any - # available function that returns None. - def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)): - return _none_constructor, _args - - else: - - def __reduce__(self, _none_constructor=type(None), _args=()): - return _none_constructor, _args + def __reduce__(self, _none_constructor=type(None), _args=()): + return _none_constructor, _args def attrib( @@ -124,11 +126,11 @@ def attrib( is used and no value is passed while instantiating or the attribute is excluded using ``init=False``. - If the value is an instance of `Factory`, its callable will be + If the value is an instance of `attrs.Factory`, its callable will be used to construct a new value (useful for mutable data types like lists or dicts). - If a default is not set (or set manually to `attr.NOTHING`), a value + If a default is not set (or set manually to `attrs.NOTHING`), a value *must* be supplied when instantiating; otherwise a `TypeError` will be raised. @@ -141,7 +143,7 @@ def attrib( :param validator: `callable` that is called by ``attrs``-generated ``__init__`` methods after the instance has been initialized. They - receive the initialized instance, the `Attribute`, and the + receive the initialized instance, the :func:`~attrs.Attribute`, and the passed value. The return value is *not* inspected so the validator has to throw an @@ -165,13 +167,25 @@ def attrib( as-is, i.e. it will be used directly *instead* of calling ``repr()`` (the default). :type repr: a `bool` or a `callable` to use a custom function. - :param bool eq: If ``True`` (default), include this attribute in the + + :param eq: If ``True`` (default), include this attribute in the generated ``__eq__`` and ``__ne__`` methods that check two instances - for equality. - :param bool order: If ``True`` (default), include this attributes in the + for equality. To override how the attribute value is compared, + pass a ``callable`` that takes a single value and returns the value + to be compared. + :type eq: a `bool` or a `callable`. + + :param order: If ``True`` (default), include this attributes in the generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. - :param bool cmp: Setting to ``True`` is equivalent to setting ``eq=True, - order=True``. Deprecated in favor of *eq* and *order*. + To override how the attribute value is ordered, + pass a ``callable`` that takes a single value and returns the value + to be ordered. + :type order: a `bool` or a `callable`. + + :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the + same value. Must not be mixed with *eq* or *order*. + :type cmp: a `bool` or a `callable`. + :param Optional[bool] hash: Include this attribute in the generated ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This is the correct behavior according the Python spec. Setting this value @@ -189,7 +203,7 @@ def attrib( components. See `extending_metadata`. :param type: The type of the attribute. In Python 3.6 or greater, the preferred method to specify the type is using a variable annotation - (see `PEP 526 `_). + (see :pep:`526`). This argument is provided for backward compatibility. Regardless of the approach used, the type will be stored on ``Attribute.type``. @@ -202,10 +216,10 @@ def attrib( parameter is ignored). :param on_setattr: Allows to overwrite the *on_setattr* setting from `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. - Set to `attr.setters.NO_OP` to run **no** `setattr` hooks for this + Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this attribute -- regardless of the setting in `attr.s`. :type on_setattr: `callable`, or a list of callables, or `None`, or - `attr.setters.NO_OP` + `attrs.setters.NO_OP` .. versionadded:: 15.2.0 *convert* .. versionadded:: 16.3.0 *metadata* @@ -219,14 +233,19 @@ def attrib( .. versionadded:: 18.1.0 ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. .. versionadded:: 18.2.0 *kw_only* - .. versionchanged:: 19.2.0 *convert* keyword argument removed + .. versionchanged:: 19.2.0 *convert* keyword argument removed. .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. .. versionadded:: 19.2.0 *eq* and *order* .. versionadded:: 20.1.0 *on_setattr* .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated """ - eq, order = _determine_eq_order(cmp, eq, order, True) + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) if hash is not None and hash is not True and hash is not False: raise TypeError( @@ -268,11 +287,50 @@ def attrib( type=type, kw_only=kw_only, eq=eq, + eq_key=eq_key, order=order, + order_key=order_key, on_setattr=on_setattr, ) +def _compile_and_eval(script, globs, locs=None, filename=""): + """ + "Exec" the script with the given global (globs) and local (locs) variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _make_method(name, script, filename, globs): + """ + Create the method with the script given and return the method object. + """ + locs = {} + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + else: + filename = "{}-{}>".format(base_filename[:-1], count) + count += 1 + + _compile_and_eval(script, globs, locs, filename) + + return locs[name] + + def _make_attr_tuple_class(cls_name, attr_names): """ Create a tuple subclass to hold `Attribute`s for an `attrs` class. @@ -296,8 +354,7 @@ class MyClassAttributes(tuple): else: attr_class_template.append(" pass") globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} - eval(compile("\n".join(attr_class_template), "", "exec"), globs) - + _compile_and_eval("\n".join(attr_class_template), globs) return globs[attr_class_name] @@ -324,7 +381,13 @@ def _is_class_var(annot): annotations which would put attrs-based classes at a performance disadvantage compared to plain old classes. """ - return str(annot).startswith(_classvar_prefixes) + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_classvar_prefixes) def _has_own_attribute(cls, attrib_name): @@ -438,7 +501,7 @@ def _transform_attrs( anns = _get_annotations(cls) if these is not None: - ca_list = [(name, ca) for name, ca in iteritems(these)] + ca_list = [(name, ca) for name, ca in these.items()] if not isinstance(these, ordered_dict): ca_list.sort(key=_counter_getter) @@ -498,15 +561,11 @@ def _transform_attrs( cls, {a.name for a in own_attrs} ) - attr_names = [a.name for a in base_attrs + own_attrs] - - AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) - if kw_only: own_attrs = [a.evolve(kw_only=True) for a in own_attrs] base_attrs = [a.evolve(kw_only=True) for a in base_attrs] - attrs = AttrsClass(base_attrs + own_attrs) + attrs = base_attrs + own_attrs # Mandatory vs non-mandatory attr order only matters when they are part of # the __init__ signature and when they aren't kw_only (which are moved to @@ -525,7 +584,13 @@ def _transform_attrs( if field_transformer is not None: attrs = field_transformer(cls, attrs) - return _Attributes((attrs, base_attrs, base_attr_map)) + + # Create AttrsClass *after* applying the field_transformer since it may + # add or remove attributes! + attr_names = [a.name for a in attrs] + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) if PYPY: @@ -543,7 +608,6 @@ def _frozen_setattrs(self, name, value): raise FrozenInstanceError() - else: def _frozen_setattrs(self, name, value): @@ -560,7 +624,7 @@ def _frozen_delattrs(self, name): raise FrozenInstanceError() -class _ClassBuilder(object): +class _ClassBuilder: """ Iteratively build *one* class. """ @@ -575,12 +639,13 @@ class _ClassBuilder(object): "_cls_dict", "_delete_attribs", "_frozen", + "_has_pre_init", "_has_post_init", "_is_exc", "_on_setattr", "_slots", "_weakref_slot", - "_has_own_setattr", + "_wrote_own_setattr", "_has_custom_setattr", ) @@ -613,20 +678,21 @@ def __init__( self._cls = cls self._cls_dict = dict(cls.__dict__) if slots else {} self._attrs = attrs - self._base_names = set(a.name for a in base_attrs) + self._base_names = {a.name for a in base_attrs} self._base_attr_map = base_map self._attr_names = tuple(a.name for a in attrs) self._slots = slots self._frozen = frozen self._weakref_slot = weakref_slot self._cache_hash = cache_hash + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) self._delete_attribs = not bool(these) self._is_exc = is_exc self._on_setattr = on_setattr self._has_custom_setattr = has_custom_setattr - self._has_own_setattr = False + self._wrote_own_setattr = False self._cls_dict["__attrs_attrs__"] = self._attrs @@ -634,7 +700,33 @@ def __init__( self._cls_dict["__setattr__"] = _frozen_setattrs self._cls_dict["__delattr__"] = _frozen_delattrs - self._has_own_setattr = True + self._wrote_own_setattr = True + elif on_setattr in ( + _ng_default_on_setattr, + setters.validate, + setters.convert, + ): + has_validator = has_converter = False + for a in attrs: + if a.validator is not None: + has_validator = True + if a.converter is not None: + has_converter = True + + if has_validator and has_converter: + break + if ( + ( + on_setattr == _ng_default_on_setattr + and not (has_validator or has_converter) + ) + or (on_setattr == setters.validate and not has_validator) + or (on_setattr == setters.convert and not has_converter) + ): + # If class-level on_setattr is set to convert + validate, but + # there's no field to convert or validate, pretend like there's + # no on_setattr. + self._on_setattr = None if getstate_setstate: ( @@ -684,13 +776,13 @@ def _patch_original_class(self): # If we've inherited an attrs __setattr__ and don't write our own, # reset it to object's. - if not self._has_own_setattr and getattr( + if not self._wrote_own_setattr and getattr( cls, "__attrs_own_setattr__", False ): cls.__attrs_own_setattr__ = False if not self._has_custom_setattr: - cls.__setattr__ = object.__setattr__ + cls.__setattr__ = _obj_setattr return cls @@ -698,10 +790,9 @@ def _create_slots_class(self): """ Build and return a new class with a `__slots__` attribute. """ - base_names = self._base_names cd = { k: v - for k, v in iteritems(self._cls_dict) + for k, v in self._cls_dict.items() if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") } @@ -713,21 +804,30 @@ def _create_slots_class(self): # XXX: a non-attrs class and subclass the resulting class with an attrs # XXX: class. See `test_slotted_confused` for details. For now that's # XXX: OK with us. - if not self._has_own_setattr: + if not self._wrote_own_setattr: cd["__attrs_own_setattr__"] = False if not self._has_custom_setattr: for base_cls in self._cls.__bases__: if base_cls.__dict__.get("__attrs_own_setattr__", False): - cd["__setattr__"] = object.__setattr__ + cd["__setattr__"] = _obj_setattr break - # Traverse the MRO to check for an existing __weakref__. + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = dict() weakref_inherited = False for base_cls in self._cls.__mro__[1:-1]: if base_cls.__dict__.get("__weakref__", None) is not None: weakref_inherited = True - break + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) names = self._attr_names if ( @@ -741,19 +841,28 @@ def _create_slots_class(self): # We only add the names of attributes that aren't inherited. # Setting __slots__ to inherited attributes wastes memory. slot_names = [name for name in names if name not in base_names] + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overridden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in existing_slots.items() + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) if self._cache_hash: slot_names.append(_hash_cache_field) cd["__slots__"] = tuple(slot_names) - qualname = getattr(self._cls, "__qualname__", None) - if qualname is not None: - cd["__qualname__"] = qualname + cd["__qualname__"] = self._cls.__qualname__ # Create new class based on old class and our methods. cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) # The following is a fix for - # https://github.com/python-attrs/attrs/issues/102. On Python 3, + # . On Python 3, # if a method mentions `__class__` or uses the no-arg super(), the # compiler will bake a reference to the class in the method itself # as `method.__closure__`. Since we replace the class with a @@ -763,6 +872,10 @@ def _create_slots_class(self): # Class- and staticmethods hide their functions inside. # These might need to be rewritten as well. closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) else: closure_cells = getattr(item, "__closure__", None) @@ -781,7 +894,7 @@ def _create_slots_class(self): def add_repr(self, ns): self._cls_dict["__repr__"] = self._add_method_dunders( - _make_repr(self._attrs, ns=ns) + _make_repr(self._attrs, ns, self._cls) ) return self @@ -853,14 +966,41 @@ def add_init(self): _make_init( self._cls, self._attrs, + self._has_pre_init, self._has_post_init, self._frozen, self._slots, self._cache_hash, self._base_attr_map, self._is_exc, - self._on_setattr is not None - and self._on_setattr is not setters.NO_OP, + self._on_setattr, + attrs_init=False, + ) + ) + + return self + + def add_match_args(self): + self._cls_dict["__match_args__"] = tuple( + field.name + for field in self._attrs + if field.init and not field.kw_only + ) + + def add_attrs_init(self): + self._cls_dict["__attrs_init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=True, ) ) @@ -918,7 +1058,7 @@ def __setattr__(self, name, val): self._cls_dict["__attrs_own_setattr__"] = True self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) - self._has_own_setattr = True + self._wrote_own_setattr = True return self @@ -948,13 +1088,7 @@ def _add_method_dunders(self, method): return method -_CMP_DEPRECATION = ( - "The usage of `cmp` is deprecated and will be removed on or after " - "2021-06-01. Please use `eq` and `order` instead." -) - - -def _determine_eq_order(cmp, eq, order, default_eq): +def _determine_attrs_eq_order(cmp, eq, order, default_eq): """ Validate the combination of *cmp*, *eq*, and *order*. Derive the effective values of eq and order. If *eq* is None, set it to *default_eq*. @@ -964,8 +1098,6 @@ def _determine_eq_order(cmp, eq, order, default_eq): # cmp takes precedence due to bw-compatibility. if cmp is not None: - warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=3) - return cmp, cmp # If left None, equality is set to the specified default and ordering @@ -982,6 +1114,47 @@ def _determine_eq_order(cmp, eq, order, default_eq): return eq, order +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, eq_key, order, order_key + + def _determine_whether_to_implement( cls, flag, auto_detect, dunders, default=True ): @@ -993,8 +1166,6 @@ def _determine_whether_to_implement( whose presence signal that the user has implemented it themselves. Return *default* if no reason for either for or against is found. - - auto_detect must be False on Python 2. """ if flag is True or flag is False: return flag @@ -1033,6 +1204,7 @@ def attrs( getstate_setstate=None, on_setattr=None, field_transformer=None, + match_args=True, ): r""" A class decorator that adds `dunder @@ -1064,7 +1236,7 @@ def attrs( inherited from some base class). So for example by implementing ``__eq__`` on a class yourself, - ``attrs`` will deduce ``eq=False`` and won't create *neither* + ``attrs`` will deduce ``eq=False`` and will create *neither* ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible ``__ne__`` by default, so it *should* be enough to only implement ``__eq__`` in most cases). @@ -1081,7 +1253,7 @@ def attrs( *cmp*, or *hash* overrides whatever *auto_detect* would determine. *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises - a `PythonTooOldError`. + an `attrs.exceptions.PythonTooOldError`. :param bool repr: Create a ``__repr__`` method with a human readable representation of ``attrs`` attributes.. @@ -1097,10 +1269,8 @@ def attrs( ``__gt__``, and ``__ge__`` methods that behave like *eq* above and allow instances to be ordered. If ``None`` (default) mirror value of *eq*. - :param Optional[bool] cmp: Setting to ``True`` is equivalent to setting - ``eq=True, order=True``. Deprecated in favor of *eq* and *order*, has - precedence over them for backward-compatibility though. Must not be - mixed with *eq* or *order*. + :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* + and *order* to the same value. Must not be mixed with *eq* or *order*. :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method is generated according how *eq* and *frozen* are set. @@ -1121,9 +1291,16 @@ def attrs( behavior `_ for more details. :param bool init: Create a ``__init__`` method that initializes the - ``attrs`` attributes. Leading underscores are stripped for the - argument name. If a ``__attrs_post_init__`` method exists on the - class, it will be called after the class is fully initialized. + ``attrs`` attributes. Leading underscores are stripped for the argument + name. If a ``__attrs_pre_init__`` method exists on the class, it will + be called before the class is initialized. If a ``__attrs_post_init__`` + method exists on the class, it will be called after the class is fully + initialized. + + If ``init`` is ``False``, an ``__attrs_init__`` method will be + injected instead. This allows you to define a custom ``__init__`` + method that can do pre-init work such as ``super().__init__()``, + and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. :param bool slots: Create a `slotted class ` that's more memory-efficient. Slotted classes are generally superior to the default dict classes, but have some gotchas you should know about, so we @@ -1152,7 +1329,7 @@ def attrs( :param bool weakref_slot: Make instances weak-referenceable. This has no effect unless ``slots`` is also enabled. - :param bool auto_attribs: If ``True``, collect `PEP 526`_-annotated + :param bool auto_attribs: If ``True``, collect :pep:`526`-annotated attributes (Python 3.6 and later only) from the class body. In this case, you **must** annotate every field. If ``attrs`` @@ -1163,13 +1340,21 @@ def attrs( If you assign a value to those attributes (e.g. ``x: int = 42``), that value becomes the default value like if it were passed using - ``attr.ib(default=42)``. Passing an instance of `Factory` also - works as expected. + ``attr.ib(default=42)``. Passing an instance of `attrs.Factory` also + works as expected in most cases (see warning below). Attributes annotated as `typing.ClassVar`, and attributes that are neither annotated nor set to an `attr.ib` are **ignored**. - .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ + .. warning:: + For features that use the attribute name to create decorators (e.g. + `validators `), you still *must* assign `attr.ib` to + them. Otherwise Python will either not find the name or try to use + the default value to call e.g. ``validator`` on it. + + These errors can be quite confusing and probably the most common bug + report on our bug tracker. + :param bool kw_only: Make all attributes keyword-only (Python 3+) in the generated ``__init__`` (if ``init`` is ``False``, this parameter is ignored). @@ -1196,7 +1381,7 @@ def attrs( :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` collects attributes from base classes. The default behavior is incorrect in certain cases of multiple inheritance. It should be on by - default but is kept off for backward-compatability. + default but is kept off for backward-compatibility. See issue `#428 `_ for more details. @@ -1226,7 +1411,9 @@ def attrs( the callable. If a list of callables is passed, they're automatically wrapped in an - `attr.setters.pipe`. + `attrs.setters.pipe`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attrs.setters.NO_OP` :param Optional[callable] field_transformer: A function that is called with the original class object and all @@ -1234,6 +1421,12 @@ def attrs( this, e.g., to automatically add converters or validators to fields based on their types. See `transform-fields` for more details. + :param bool match_args: + If `True` (default), set ``__match_args__`` on the class to support + :pep:`634` (Structural Pattern Matching). It is a tuple of all + non-keyword-only ``__init__`` parameter names on Python 3.10 and later. + Ignored on older Python versions. + .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* .. versionadded:: 16.3.0 *str* @@ -1263,23 +1456,19 @@ def attrs( .. versionadded:: 20.1.0 *getstate_setstate* .. versionadded:: 20.1.0 *on_setattr* .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 21.3.0 *match_args* """ - if auto_detect and PY2: - raise PythonTooOldError( - "auto_detect only works on Python 3 and later." - ) - - eq_, order_ = _determine_eq_order(cmp, eq, order, None) + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) hash_ = hash # work around the lack of nonlocal if isinstance(on_setattr, (list, tuple)): on_setattr = setters.pipe(*on_setattr) def wrap(cls): - - if getattr(cls, "__class__", None) is None: - raise TypeError("attrs only works with new-style classes.") - is_frozen = frozen or _has_frozen_base_class(cls) is_exc = auto_exc is True and issubclass(cls, BaseException) has_own_setattr = auto_detect and _has_own_attribute( @@ -1372,12 +1561,20 @@ def wrap(cls): ): builder.add_init() else: + builder.add_attrs_init() if cache_hash: raise TypeError( "Invalid value for cache_hash. To use hash caching," " init must be True." ) + if ( + PY310 + and match_args + and not _has_own_attribute(cls, "__match_args__") + ): + builder.add_match_args() + return builder.build_class() # maybe_cls's type depends on the usage of the decorator. It's a class @@ -1395,65 +1592,24 @@ def wrap(cls): """ -if PY2: - - def _has_frozen_base_class(cls): - """ - Check whether *cls* has a frozen ancestor by looking at its - __setattr__. - """ - return ( - getattr(cls.__setattr__, "__module__", None) - == _frozen_setattrs.__module__ - and cls.__setattr__.__name__ == _frozen_setattrs.__name__ - ) - - -else: - - def _has_frozen_base_class(cls): - """ - Check whether *cls* has a frozen ancestor by looking at its - __setattr__. - """ - return cls.__setattr__ == _frozen_setattrs - - -def _attrs_to_tuple(obj, attrs): +def _has_frozen_base_class(cls): """ - Create a tuple of all values of *obj*'s *attrs*. + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. """ - return tuple(getattr(obj, a.name) for a in attrs) + return cls.__setattr__ is _frozen_setattrs def _generate_unique_filename(cls, func_name): """ Create a "filename" suitable for a function being generated. """ - unique_id = uuid.uuid4() - extra = "" - count = 1 - - while True: - unique_filename = "".format( - func_name, - cls.__module__, - getattr(cls, "__qualname__", cls.__name__), - extra, - ) - # To handle concurrency we essentially "reserve" our spot in - # the linecache with a dummy line. The caller can then - # set this value correctly. - cache_line = (1, None, (str(unique_id),), unique_filename) - if ( - linecache.cache.setdefault(unique_filename, cache_line) - == cache_line - ): - return unique_filename - - # Looks like this spot is taken. Try again. - count += 1 - extra = "-{0}".format(count) + unique_filename = "".format( + func_name, + cls.__module__, + getattr(cls, "__qualname__", cls.__name__), + ) + return unique_filename def _make_hash(cls, attrs, frozen, cache_hash): @@ -1465,6 +1621,8 @@ def _make_hash(cls, attrs, frozen, cache_hash): unique_filename = _generate_unique_filename(cls, "hash") type_hash = hash(unique_filename) + # If eq is custom generated, we need to include the functions in globs + globs = {} hash_def = "def __hash__(self" hash_func = "hash((" @@ -1472,8 +1630,7 @@ def _make_hash(cls, attrs, frozen, cache_hash): if not cache_hash: hash_def += "):" else: - if not PY2: - hash_def += ", *" + hash_def += ", *" hash_def += ( ", _cache_wrapper=" @@ -1499,7 +1656,14 @@ def append_hash_computation_lines(prefix, indent): ) for a in attrs: - method_lines.append(indent + " self.%s," % a.name) + if a.eq_key: + cmp_name = "_%s_key" % (a.name,) + globs[cmp_name] = a.eq_key + method_lines.append( + indent + " %s(self.%s)," % (cmp_name, a.name) + ) + else: + method_lines.append(indent + " self.%s," % a.name) method_lines.append(indent + " " + closing_braces) @@ -1519,21 +1683,7 @@ def append_hash_computation_lines(prefix, indent): append_hash_computation_lines("return ", tab) script = "\n".join(method_lines) - globs = {} - locs = {} - bytecode = compile(script, unique_filename, "exec") - eval(bytecode, globs, locs) - - # In order of debuggers like PDB being able to step through the code, - # we add a fake linecache entry. - linecache.cache[unique_filename] = ( - len(script), - None, - script.splitlines(True), - unique_filename, - ) - - return locs["__hash__"] + return _make_method("__hash__", script, unique_filename, globs) def _add_hash(cls, attrs): @@ -1575,34 +1725,44 @@ def _make_eq(cls, attrs): " if other.__class__ is not self.__class__:", " return NotImplemented", ] + # We can't just do a big self.x = other.x and... clause due to # irregularities like nan == nan is false but (nan,) == (nan,) is true. + globs = {} if attrs: lines.append(" return (") others = [" ) == ("] for a in attrs: - lines.append(" self.%s," % (a.name,)) - others.append(" other.%s," % (a.name,)) + if a.eq_key: + cmp_name = "_%s_key" % (a.name,) + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append( + " %s(self.%s)," + % ( + cmp_name, + a.name, + ) + ) + others.append( + " %s(other.%s)," + % ( + cmp_name, + a.name, + ) + ) + else: + lines.append(" self.%s," % (a.name,)) + others.append(" other.%s," % (a.name,)) lines += others + [" )"] else: lines.append(" return True") script = "\n".join(lines) - globs = {} - locs = {} - bytecode = compile(script, unique_filename, "exec") - eval(bytecode, globs, locs) - # In order of debuggers like PDB being able to step through the code, - # we add a fake linecache entry. - linecache.cache[unique_filename] = ( - len(script), - None, - script.splitlines(True), - unique_filename, - ) - return locs["__eq__"] + return _make_method("__eq__", script, unique_filename, globs) def _make_order(cls, attrs): @@ -1615,7 +1775,12 @@ def attrs_to_tuple(obj): """ Save us some typing. """ - return _attrs_to_tuple(obj, attrs) + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) def __lt__(self, other): """ @@ -1669,66 +1834,126 @@ def _add_eq(cls, attrs=None): return cls -_already_repring = threading.local() +if HAS_F_STRINGS: + def _make_repr(attrs, ns, cls): + unique_filename = _generate_unique_filename(cls, "repr") + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r + for name, r, _ in attr_names_with_reprs + if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name + if i + else 'getattr(self, "' + name + '", NOTHING)' + ) + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) + ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) -def _make_repr(attrs, ns): - """ - Make a repr method that includes relevant *attrs*, adding *ns* to the full - name. - """ + if ns is None: + cls_name_fragment = ( + '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + ) + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" + + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + " return f'%s(%s)'" % (cls_name_fragment, repr_fragment), + " finally:", + " already_repring.remove(id(self))", + ] + + return _make_method( + "__repr__", "\n".join(lines), unique_filename, globs=globs + ) - # Figure out which attributes to include, and which function to use to - # format them. The a.repr value can be either bool or a custom callable. - attr_names_with_reprs = tuple( - (a.name, repr if a.repr is True else a.repr) - for a in attrs - if a.repr is not False - ) +else: - def __repr__(self): + def _make_repr(attrs, ns, _): """ - Automatically created by attrs. + Make a repr method that includes relevant *attrs*, adding *ns* to the + full name. """ - try: - working_set = _already_repring.working_set - except AttributeError: - working_set = set() - _already_repring.working_set = working_set - if id(self) in working_set: - return "..." - real_cls = self.__class__ - if ns is None: - qualname = getattr(real_cls, "__qualname__", None) - if qualname is not None: - class_name = qualname.rsplit(">.", 1)[-1] + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, repr if a.repr is True else a.repr) + for a in attrs + if a.repr is not False + ) + + def __repr__(self): + """ + Automatically created by attrs. + """ + try: + already_repring = _compat.repr_context.already_repring + except AttributeError: + already_repring = set() + _compat.repr_context.already_repring = already_repring + + if id(self) in already_repring: + return "..." + real_cls = self.__class__ + if ns is None: + class_name = real_cls.__qualname__.rsplit(">.", 1)[-1] else: - class_name = real_cls.__name__ - else: - class_name = ns + "." + real_cls.__name__ + class_name = ns + "." + real_cls.__name__ - # Since 'self' remains on the stack (i.e.: strongly referenced) for the - # duration of this call, it's safe to depend on id(...) stability, and - # not need to track the instance and therefore worry about properties - # like weakref- or hash-ability. - working_set.add(id(self)) - try: - result = [class_name, "("] - first = True - for name, attr_repr in attr_names_with_reprs: - if first: - first = False - else: - result.append(", ") - result.extend( - (name, "=", attr_repr(getattr(self, name, NOTHING))) - ) - return "".join(result) + ")" - finally: - working_set.remove(id(self)) + # Since 'self' remains on the stack (i.e.: strongly referenced) + # for the duration of this call, it's safe to depend on id(...) + # stability, and not need to track the instance and therefore + # worry about properties like weakref- or hash-ability. + already_repring.add(id(self)) + try: + result = [class_name, "("] + first = True + for name, attr_repr in attr_names_with_reprs: + if first: + first = False + else: + result.append(", ") + result.extend( + (name, "=", attr_repr(getattr(self, name, NOTHING))) + ) + return "".join(result) + ")" + finally: + already_repring.remove(id(self)) - return __repr__ + return __repr__ def _add_repr(cls, ns=None, attrs=None): @@ -1738,7 +1963,7 @@ def _add_repr(cls, ns=None, attrs=None): if attrs is None: attrs = cls.__attrs_attrs__ - cls.__repr__ = _make_repr(attrs, ns) + cls.__repr__ = _make_repr(attrs, ns, cls) return cls @@ -1755,12 +1980,12 @@ def fields(cls): :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` class. - :rtype: tuple (with name accessors) of `attr.Attribute` + :rtype: tuple (with name accessors) of `attrs.Attribute` .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields by name. """ - if not isclass(cls): + if not isinstance(cls, type): raise TypeError("Passed object must be a class.") attrs = getattr(cls, "__attrs_attrs__", None) if attrs is None: @@ -1782,20 +2007,20 @@ def fields_dict(cls): class. :rtype: an ordered dict where keys are attribute names and values are - `attr.Attribute`\\ s. This will be a `dict` if it's + `attrs.Attribute`\\ s. This will be a `dict` if it's naturally ordered like on Python 3.6+ or an :class:`~collections.OrderedDict` otherwise. .. versionadded:: 18.1.0 """ - if not isclass(cls): + if not isinstance(cls, type): raise TypeError("Passed object must be a class.") attrs = getattr(cls, "__attrs_attrs__", None) if attrs is None: raise NotAnAttrsClassError( "{cls!r} is not an attrs-decorated class.".format(cls=cls) ) - return ordered_dict(((a.name, a) for a in attrs)) + return ordered_dict((a.name, a) for a in attrs) def validate(inst): @@ -1829,15 +2054,21 @@ def _is_slot_attr(a_name, base_attr_map): def _make_init( cls, attrs, + pre_init, post_init, frozen, slots, cache_hash, base_attr_map, is_exc, - has_global_on_setattr, + cls_on_setattr, + attrs_init, ): - if frozen and has_global_on_setattr: + has_cls_on_setattr = ( + cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP + ) + + if frozen and has_cls_on_setattr: raise ValueError("Frozen classes can't use on_setattr.") needs_cached_setattr = cache_hash or frozen @@ -1855,9 +2086,7 @@ def _make_init( raise ValueError("Frozen classes can't use on_setattr.") needs_cached_setattr = True - elif ( - has_global_on_setattr and a.on_setattr is not setters.NO_OP - ) or _is_slot_attr(a.name, base_attr_map): + elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: needs_cached_setattr = True unique_filename = _generate_unique_filename(cls, "init") @@ -1866,44 +2095,41 @@ def _make_init( filtered_attrs, frozen, slots, + pre_init, post_init, cache_hash, base_attr_map, is_exc, - needs_cached_setattr, - has_global_on_setattr, + has_cls_on_setattr, + attrs_init, ) - locs = {} - bytecode = compile(script, unique_filename, "exec") + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) if needs_cached_setattr: # Save the lookup overhead in __init__ if we need to circumvent # setattr hooks. - globs["_cached_setattr"] = _obj_setattr - - eval(bytecode, globs, locs) + globs["_setattr"] = _obj_setattr - # In order of debuggers like PDB being able to step through the code, - # we add a fake linecache entry. - linecache.cache[unique_filename] = ( - len(script), - None, - script.splitlines(True), + init = _make_method( + "__attrs_init__" if attrs_init else "__init__", + script, unique_filename, + globs, ) + init.__annotations__ = annotations - __init__ = locs["__init__"] - __init__.__annotations__ = annotations - - return __init__ + return init def _setattr(attr_name, value_var, has_on_setattr): """ Use the cached object.setattr to set *attr_name* to *value_var*. """ - return "_setattr('%s', %s)" % (attr_name, value_var) + return "_setattr(self, '%s', %s)" % (attr_name, value_var) def _setattr_with_converter(attr_name, value_var, has_on_setattr): @@ -1911,7 +2137,7 @@ def _setattr_with_converter(attr_name, value_var, has_on_setattr): Use the cached object.setattr to set *attr_name* to *value_var*, but run its converter first. """ - return "_setattr('%s', %s(%s))" % ( + return "_setattr(self, '%s', %s(%s))" % ( attr_name, _init_converter_pat % (attr_name,), value_var, @@ -1944,73 +2170,17 @@ def _assign_with_converter(attr_name, value_var, has_on_setattr): ) -if PY2: - - def _unpack_kw_only_py2(attr_name, default=None): - """ - Unpack *attr_name* from _kw_only dict. - """ - if default is not None: - arg_default = ", %s" % default - else: - arg_default = "" - return "%s = _kw_only.pop('%s'%s)" % ( - attr_name, - attr_name, - arg_default, - ) - - def _unpack_kw_only_lines_py2(kw_only_args): - """ - Unpack all *kw_only_args* from _kw_only dict and handle errors. - - Given a list of strings "{attr_name}" and "{attr_name}={default}" - generates list of lines of code that pop attrs from _kw_only dict and - raise TypeError similar to builtin if required attr is missing or - extra key is passed. - - >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) - try: - a = _kw_only.pop('a') - b = _kw_only.pop('b', 42) - except KeyError as _key_error: - raise TypeError( - ... - if _kw_only: - raise TypeError( - ... - """ - lines = ["try:"] - lines.extend( - " " + _unpack_kw_only_py2(*arg.split("=")) - for arg in kw_only_args - ) - lines += """\ -except KeyError as _key_error: - raise TypeError( - '__init__() missing required keyword-only argument: %s' % _key_error - ) -if _kw_only: - raise TypeError( - '__init__() got an unexpected keyword argument %r' - % next(iter(_kw_only)) - ) -""".split( - "\n" - ) - return lines - - def _attrs_to_init_script( attrs, frozen, slots, + pre_init, post_init, cache_hash, base_attr_map, is_exc, - needs_cached_setattr, - has_global_on_setattr, + has_cls_on_setattr, + attrs_init, ): """ Return a script of an initializer for *attrs* and a dict of globals. @@ -2021,13 +2191,8 @@ def _attrs_to_init_script( a cached ``object.__setattr__``. """ lines = [] - if needs_cached_setattr: - lines.append( - # Circumvent the __setattr__ descriptor to save one lookup per - # assignment. - # Note _setattr will be used again below if cache_hash is True - "_setattr = _cached_setattr.__get__(self, self.__class__)" - ) + if pre_init: + lines.append("self.__attrs_pre_init__()") if frozen is True: if slots is True: @@ -2080,7 +2245,7 @@ def fmt_setter_with_converter( attr_name = a.name has_on_setattr = a.on_setattr is not None or ( - a.on_setattr is not setters.NO_OP and has_global_on_setattr + a.on_setattr is not setters.NO_OP and has_cls_on_setattr ) arg_name = a.name.lstrip("_") @@ -2210,8 +2375,14 @@ def fmt_setter_with_converter( else: lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) - if a.init is True and a.converter is None and a.type is not None: - annotations[arg_name] = a.type + if a.init is True: + if a.type is not None and a.converter is None: + annotations[arg_name] = a.type + elif a.converter is not None: + # Try to get the type from the converter. + t = _AnnotationExtractor(a.converter).get_first_param_type() + if t: + annotations[arg_name] = t if attrs_to_validate: # we can skip this if there are no validators. names_for_globals["_config"] = _config @@ -2228,7 +2399,7 @@ def fmt_setter_with_converter( if post_init: lines.append("self.__attrs_post_init__()") - # because this is set only after __attrs_post_init is called, a crash + # because this is set only after __attrs_post_init__ is called, a crash # will result if post-init tries to access the hash code. This seemed # preferable to setting this beforehand, in which case alteration to # field values during post-init combined with post-init accessing the @@ -2237,7 +2408,7 @@ def fmt_setter_with_converter( if frozen: if slots: # if frozen and slots, then _setattr defined above - init_hash_cache = "_setattr('%s', %s)" + init_hash_cache = "_setattr(self, '%s', %s)" else: # if frozen and not slots, then _inst_dict defined above init_hash_cache = "_inst_dict['%s'] = %s" @@ -2254,49 +2425,54 @@ def fmt_setter_with_converter( args = ", ".join(args) if kw_only_args: - if PY2: - lines = _unpack_kw_only_lines_py2(kw_only_args) + lines - - args += "%s**_kw_only" % (", " if args else "",) # leading comma - else: - args += "%s*, %s" % ( - ", " if args else "", # leading comma - ", ".join(kw_only_args), # kw_only args - ) + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) return ( """\ -def __init__(self, {args}): +def {init_name}(self, {args}): {lines} """.format( - args=args, lines="\n ".join(lines) if lines else "pass" + init_name=("__attrs_init__" if attrs_init else "__init__"), + args=args, + lines="\n ".join(lines) if lines else "pass", ), names_for_globals, annotations, ) -class Attribute(object): +class Attribute: """ *Read-only* representation of an attribute. + The class has *all* arguments of `attr.ib` (except for ``factory`` + which is only syntactic sugar for ``default=Factory(...)`` plus the + following: + + - ``name`` (`str`): The name of the attribute. + - ``inherited`` (`bool`): Whether or not that attribute has been inherited + from a base class. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables + that are used for comparing and ordering objects by this attribute, + respectively. These are set by passing a callable to `attr.ib`'s ``eq``, + ``order``, or ``cmp`` arguments. See also :ref:`comparison customization + `. + Instances of this class are frequently used for introspection purposes like: - `fields` returns a tuple of them. - Validators get them passed as the first argument. - - The *field transformer* hook receives a list of them. - - :attribute name: The name of the attribute. - :attribute inherited: Whether or not that attribute has been inherited from - a base class. - - Plus *all* arguments of `attr.ib` (except for ``factory`` - which is only syntactic sugar for ``default=Factory(...)``. + - The :ref:`field transformer ` hook receives a list of + them. .. versionadded:: 20.1.0 *inherited* .. versionadded:: 20.1.0 *on_setattr* .. versionchanged:: 20.2.0 *inherited* is not taken into account for equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* For the full version history of the fields, see `attr.ib`. """ @@ -2307,7 +2483,9 @@ class Attribute(object): "validator", "repr", "eq", + "eq_key", "order", + "order_key", "hash", "init", "metadata", @@ -2333,10 +2511,14 @@ def __init__( converter=None, kw_only=False, eq=None, + eq_key=None, order=None, + order_key=None, on_setattr=None, ): - eq, order = _determine_eq_order(cmp, eq, order, True) + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) # Cache this descriptor here to speed things up later. bound_setattr = _obj_setattr.__get__(self, Attribute) @@ -2348,14 +2530,16 @@ def __init__( bound_setattr("validator", validator) bound_setattr("repr", repr) bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) bound_setattr("order", order) + bound_setattr("order_key", order_key) bound_setattr("hash", hash) bound_setattr("init", init) bound_setattr("converter", converter) bound_setattr( "metadata", ( - metadata_proxy(metadata) + types.MappingProxyType(dict(metadata)) # Shallow copy if metadata else _empty_metadata_singleton ), @@ -2399,15 +2583,6 @@ def from_counting_attr(cls, name, ca, type=None): **inst_dict ) - @property - def cmp(self): - """ - Simulate the presence of a cmp attribute and warn. - """ - warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2) - - return self.eq and self.order - # Don't use attr.evolve since fields(Attribute) doesn't work def evolve(self, **changes): """ @@ -2450,7 +2625,7 @@ def _setattrs(self, name_values_pairs): else: bound_setattr( name, - metadata_proxy(value) + types.MappingProxyType(dict(value)) if value else _empty_metadata_singleton, ) @@ -2481,7 +2656,7 @@ def _setattrs(self, name_values_pairs): ) -class _CountingAttr(object): +class _CountingAttr: """ Intermediate representation of attributes that uses a counter to preserve the order in which the attributes have been defined. @@ -2495,7 +2670,9 @@ class _CountingAttr(object): "_default", "repr", "eq", + "eq_key", "order", + "order_key", "hash", "init", "metadata", @@ -2516,7 +2693,9 @@ class _CountingAttr(object): init=True, kw_only=False, eq=True, + eq_key=None, order=False, + order_key=None, inherited=False, on_setattr=None, ) @@ -2541,7 +2720,9 @@ class _CountingAttr(object): init=True, kw_only=False, eq=True, + eq_key=None, order=False, + order_key=None, inherited=False, on_setattr=None, ), @@ -2553,7 +2734,7 @@ def __init__( default, validator, repr, - cmp, # XXX: unused, remove along with cmp + cmp, hash, init, converter, @@ -2561,7 +2742,9 @@ def __init__( type, kw_only, eq, + eq_key, order, + order_key, on_setattr, ): _CountingAttr.cls_counter += 1 @@ -2571,7 +2754,9 @@ def __init__( self.converter = converter self.repr = repr self.eq = eq + self.eq_key = eq_key self.order = order + self.order_key = order_key self.hash = hash self.init = init self.metadata = metadata @@ -2614,12 +2799,11 @@ def default(self, meth): _CountingAttr = _add_eq(_add_repr(_CountingAttr)) -@attrs(slots=True, init=False, hash=True) -class Factory(object): +class Factory: """ Stores a factory callable. - If passed as the default value to `attr.ib`, the factory is used to + If passed as the default value to `attrs.field`, the factory is used to generate a new value. :param callable factory: A callable that takes either none or exactly one @@ -2630,8 +2814,7 @@ class Factory(object): .. versionadded:: 17.1.0 *takes_self* """ - factory = attrib() - takes_self = attrib() + __slots__ = ("factory", "takes_self") def __init__(self, factory, takes_self=False): """ @@ -2641,6 +2824,38 @@ def __init__(self, factory, takes_self=False): self.factory = factory self.takes_self = takes_self + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + def make_class(name, attrs, bases=(object,), **attributes_arguments): """ @@ -2670,16 +2885,24 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): if isinstance(attrs, dict): cls_dict = attrs elif isinstance(attrs, (list, tuple)): - cls_dict = dict((a, attrib()) for a in attrs) + cls_dict = {a: attrib() for a in attrs} else: raise TypeError("attrs argument must be a dict or a list.") + pre_init = cls_dict.pop("__attrs_pre_init__", None) post_init = cls_dict.pop("__attrs_post_init__", None) - type_ = type( - name, - bases, - {} if post_init is None else {"__attrs_post_init__": post_init}, - ) + user_init = cls_dict.pop("__init__", None) + + body = {} + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) + # For pickling to work, the __module__ variable needs to be set to the # frame where the class is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not @@ -2696,7 +2919,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): ( attributes_arguments["eq"], attributes_arguments["order"], - ) = _determine_eq_order( + ) = _determine_attrs_eq_order( cmp, attributes_arguments.get("eq"), attributes_arguments.get("order"), @@ -2711,7 +2934,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): @attrs(slots=True, hash=True) -class _AndValidator(object): +class _AndValidator: """ Compose many validators to a single one. """ @@ -2751,6 +2974,9 @@ def pipe(*converters): When called on a value, it runs all wrapped converters, returning the *last* value. + Type annotations will be inferred from the wrapped converters', if + they have any. + :param callables converters: Arbitrary number of converters. .. versionadded:: 20.1.0 @@ -2762,4 +2988,19 @@ def pipe_converter(val): return val + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__ = {"val": A, "return": A} + else: + # Get parameter type from first converter. + t = _AnnotationExtractor(converters[0]).get_first_param_type() + if t: + pipe_converter.__annotations__["val"] = t + + # Get return type from last converter. + rt = _AnnotationExtractor(converters[-1]).get_return_type() + if rt: + pipe_converter.__annotations__["return"] = rt + return pipe_converter diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_next_gen.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_next_gen.py index 2b5565c56..5a06a7438 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/_next_gen.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_next_gen.py @@ -1,16 +1,24 @@ -""" -This is a Python 3.6 and later-only, keyword-only, and **provisional** API that -calls `attr.s` with different default values. +# SPDX-License-Identifier: MIT -Provisional APIs that shall become "import attrs" one glorious day. +""" +These are Python 3.6+-only and keyword-only APIs that call `attr.s` and +`attr.ib` with different default values. """ -from functools import partial -from attr.exceptions import UnannotatedAttributeError +from functools import partial from . import setters -from ._make import NOTHING, _frozen_setattrs, attrib, attrs +from ._funcs import asdict as _asdict +from ._funcs import astuple as _astuple +from ._make import ( + NOTHING, + _frozen_setattrs, + _ng_default_on_setattr, + attrib, + attrs, +) +from .exceptions import UnannotatedAttributeError def define( @@ -34,22 +42,45 @@ def define( getstate_setstate=None, on_setattr=None, field_transformer=None, + match_args=True, ): r""" - The only behavioral differences are the handling of the *auto_attribs* - option: + Define an ``attrs`` class. + + Differences to the classic `attr.s` that it uses underneath: + + - Automatically detect whether or not *auto_attribs* should be `True` (c.f. + *auto_attribs* parameter). + - If *frozen* is `False`, run converters and validators when setting an + attribute by default. + - *slots=True* + + .. caution:: + + Usually this has only upsides and few visible effects in everyday + programming. But it *can* lead to some suprising behaviors, so please + make sure to read :term:`slotted classes`. + - *auto_exc=True* + - *auto_detect=True* + - *order=False* + - Some options that were only relevant on Python 2 or were kept around for + backwards-compatibility have been removed. + + Please note that these are all defaults and you can change them as you + wish. :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves exactly like `attr.s`. If left `None`, `attr.s` will try to guess: - 1. If all attributes are annotated and no `attr.ib` is found, it assumes - *auto_attribs=True*. + 1. If any attributes are annotated and no unannotated `attrs.fields`\ s + are found, it assumes *auto_attribs=True*. 2. Otherwise it assumes *auto_attribs=False* and tries to collect - `attr.ib`\ s. + `attrs.fields`\ s. - and that mutable classes (``frozen=False``) validate on ``__setattr__``. + For now, please refer to `attr.s` for the rest of the parameters. .. versionadded:: 20.1.0 + .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. """ def do_it(cls, auto_attribs): @@ -74,6 +105,7 @@ def do_it(cls, auto_attribs): getstate_setstate=getstate_setstate, on_setattr=on_setattr, field_transformer=field_transformer, + match_args=match_args, ) def wrap(cls): @@ -86,9 +118,9 @@ def wrap(cls): had_on_setattr = on_setattr not in (None, setters.NO_OP) - # By default, mutable classes validate on setattr. + # By default, mutable classes convert & validate on setattr. if frozen is False and on_setattr is None: - on_setattr = setters.validate + on_setattr = _ng_default_on_setattr # However, if we subclass a frozen class, we inherit the immutability # and disable on_setattr. @@ -158,3 +190,31 @@ def field( order=order, on_setattr=on_setattr, ) + + +def asdict(inst, *, recurse=True, filter=None, value_serializer=None): + """ + Same as `attr.asdict`, except that collections types are always retained + and dict is always used as *dict_factory*. + + .. versionadded:: 21.3.0 + """ + return _asdict( + inst=inst, + recurse=recurse, + filter=filter, + value_serializer=value_serializer, + retain_collection_types=True, + ) + + +def astuple(inst, *, recurse=True, filter=None): + """ + Same as `attr.astuple`, except that collections types are always retained + and `tuple` is always used as the *tuple_factory*. + + .. versionadded:: 21.3.0 + """ + return _astuple( + inst=inst, recurse=recurse, filter=filter, retain_collection_types=True + ) diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/_version_info.py b/conda_lock/_vendor/poetry/core/_vendor/attr/_version_info.py index 014e78a1b..51a1312f9 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/_version_info.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/_version_info.py @@ -1,4 +1,5 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT + from functools import total_ordering @@ -8,7 +9,7 @@ @total_ordering @attrs(eq=False, order=False, slots=True, frozen=True) -class VersionInfo(object): +class VersionInfo: """ A version object that can be compared to tuple of length 1--4: diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/converters.py b/conda_lock/_vendor/poetry/core/_vendor/attr/converters.py index 715ce1785..a73626c26 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/converters.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/converters.py @@ -1,16 +1,21 @@ +# SPDX-License-Identifier: MIT + """ Commonly useful converters. """ -from __future__ import absolute_import, division, print_function +import typing + +from ._compat import _AnnotationExtractor from ._make import NOTHING, Factory, pipe __all__ = [ - "pipe", - "optional", "default_if_none", + "optional", + "pipe", + "to_bool", ] @@ -19,6 +24,9 @@ def optional(converter): A converter that allows an attribute to be optional. An optional attribute is one which can be set to ``None``. + Type annotations will be inferred from the wrapped converter's, if it + has any. + :param callable converter: the converter that is used for non-``None`` values. @@ -30,6 +38,16 @@ def optional_converter(val): return None return converter(val) + xtr = _AnnotationExtractor(converter) + + t = xtr.get_first_param_type() + if t: + optional_converter.__annotations__["val"] = typing.Optional[t] + + rt = xtr.get_return_type() + if rt: + optional_converter.__annotations__["return"] = typing.Optional[rt] + return optional_converter @@ -39,14 +57,14 @@ def default_if_none(default=NOTHING, factory=None): result of *factory*. :param default: Value to be used if ``None`` is passed. Passing an instance - of `attr.Factory` is supported, however the ``takes_self`` option + of `attrs.Factory` is supported, however the ``takes_self`` option is *not*. - :param callable factory: A callable that takes not parameters whose result + :param callable factory: A callable that takes no parameters whose result is used if ``None`` is passed. :raises TypeError: If **neither** *default* or *factory* is passed. :raises TypeError: If **both** *default* and *factory* are passed. - :raises ValueError: If an instance of `attr.Factory` is passed with + :raises ValueError: If an instance of `attrs.Factory` is passed with ``takes_self=True``. .. versionadded:: 18.2.0 @@ -83,3 +101,44 @@ def default_if_none_converter(val): return default return default_if_none_converter + + +def to_bool(val): + """ + Convert "boolean" strings (e.g., from env. vars.) to real booleans. + + Values mapping to :code:`True`: + + - :code:`True` + - :code:`"true"` / :code:`"t"` + - :code:`"yes"` / :code:`"y"` + - :code:`"on"` + - :code:`"1"` + - :code:`1` + + Values mapping to :code:`False`: + + - :code:`False` + - :code:`"false"` / :code:`"f"` + - :code:`"no"` / :code:`"n"` + - :code:`"off"` + - :code:`"0"` + - :code:`0` + + :raises ValueError: for any other value. + + .. versionadded:: 21.3.0 + """ + if isinstance(val, str): + val = val.lower() + truthy = {True, "true", "t", "yes", "y", "on", "1", 1} + falsy = {False, "false", "f", "no", "n", "off", "0", 0} + try: + if val in truthy: + return True + if val in falsy: + return False + except TypeError: + # Raised when "val" is not hashable (e.g., lists) + pass + raise ValueError("Cannot convert value to bool: {}".format(val)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/exceptions.py b/conda_lock/_vendor/poetry/core/_vendor/attr/exceptions.py index fcd89106f..5dc51e0a8 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/exceptions.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/exceptions.py @@ -1,9 +1,9 @@ -from __future__ import absolute_import, division, print_function +# SPDX-License-Identifier: MIT class FrozenError(AttributeError): """ - A frozen/immutable instance or attribute haave been attempted to be + A frozen/immutable instance or attribute have been attempted to be modified. It mirrors the behavior of ``namedtuples`` by using the same error message diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/filters.py b/conda_lock/_vendor/poetry/core/_vendor/attr/filters.py index dc47e8fa3..baa25e946 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/filters.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/filters.py @@ -1,10 +1,9 @@ +# SPDX-License-Identifier: MIT + """ Commonly useful filters for `attr.asdict`. """ -from __future__ import absolute_import, division, print_function - -from ._compat import isclass from ._make import Attribute @@ -13,17 +12,17 @@ def _split_what(what): Returns a tuple of `frozenset`s of classes and attributes. """ return ( - frozenset(cls for cls in what if isclass(cls)), + frozenset(cls for cls in what if isinstance(cls, type)), frozenset(cls for cls in what if isinstance(cls, Attribute)), ) def include(*what): """ - Whitelist *what*. + Include *what*. - :param what: What to whitelist. - :type what: `list` of `type` or `attr.Attribute`\\ s + :param what: What to include. + :type what: `list` of `type` or `attrs.Attribute`\\ s :rtype: `callable` """ @@ -37,10 +36,10 @@ def include_(attribute, value): def exclude(*what): """ - Blacklist *what*. + Exclude *what*. - :param what: What to blacklist. - :type what: `list` of classes or `attr.Attribute`\\ s. + :param what: What to exclude. + :type what: `list` of classes or `attrs.Attribute`\\ s. :rtype: `callable` """ diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/setters.py b/conda_lock/_vendor/poetry/core/_vendor/attr/setters.py index 240014b3c..12ed6750d 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/setters.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/setters.py @@ -1,8 +1,9 @@ +# SPDX-License-Identifier: MIT + """ Commonly used hooks for on_setattr. """ -from __future__ import absolute_import, division, print_function from . import _config from .exceptions import FrozenAttributeError @@ -67,11 +68,6 @@ def convert(instance, attrib, new_value): return new_value +# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. +# autodata stopped working, so the docstring is inlined in the API docs. NO_OP = object() -""" -Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. - -Does not work in `pipe` or within lists. - -.. versionadded:: 20.1.0 -""" diff --git a/conda_lock/_vendor/poetry/core/_vendor/attr/validators.py b/conda_lock/_vendor/poetry/core/_vendor/attr/validators.py index b9a73054e..eece517da 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attr/validators.py +++ b/conda_lock/_vendor/poetry/core/_vendor/attr/validators.py @@ -1,30 +1,98 @@ +# SPDX-License-Identifier: MIT + """ Commonly useful validators. """ -from __future__ import absolute_import, division, print_function +import operator import re +from contextlib import contextmanager + +from ._config import get_run_validators, set_run_validators from ._make import _AndValidator, and_, attrib, attrs from .exceptions import NotCallableError +try: + Pattern = re.Pattern +except AttributeError: # Python <3.7 lacks a Pattern type. + Pattern = type(re.compile("")) + + __all__ = [ "and_", "deep_iterable", "deep_mapping", + "disabled", + "ge", + "get_disabled", + "gt", "in_", "instance_of", "is_callable", + "le", + "lt", "matches_re", + "max_len", + "min_len", "optional", "provides", + "set_disabled", ] +def set_disabled(disabled): + """ + Globally disable or enable running validators. + + By default, they are run. + + :param disabled: If ``True``, disable running all validators. + :type disabled: bool + + .. warning:: + + This function is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(not disabled) + + +def get_disabled(): + """ + Return a bool indicating whether validators are currently disabled or not. + + :return: ``True`` if validators are currently disabled. + :rtype: bool + + .. versionadded:: 21.3.0 + """ + return not get_run_validators() + + +@contextmanager +def disabled(): + """ + Context manager that disables running validators within its context. + + .. warning:: + + This context manager is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(False) + try: + yield + finally: + set_run_validators(True) + + @attrs(repr=False, slots=True, hash=True) -class _InstanceOfValidator(object): +class _InstanceOfValidator: type = attrib() def __call__(self, inst, attr, value): @@ -61,16 +129,15 @@ def instance_of(type): :type type: type or tuple of types :raises TypeError: With a human readable error message, the attribute - (of type `attr.Attribute`), the expected type, and the value it + (of type `attrs.Attribute`), the expected type, and the value it got. """ return _InstanceOfValidator(type) @attrs(repr=False, frozen=True, slots=True) -class _MatchesReValidator(object): - regex = attrib() - flags = attrib() +class _MatchesReValidator: + pattern = attrib() match_func = attrib() def __call__(self, inst, attr, value): @@ -79,18 +146,18 @@ def __call__(self, inst, attr, value): """ if not self.match_func(value): raise ValueError( - "'{name}' must match regex {regex!r}" + "'{name}' must match regex {pattern!r}" " ({value!r} doesn't)".format( - name=attr.name, regex=self.regex.pattern, value=value + name=attr.name, pattern=self.pattern.pattern, value=value ), attr, - self.regex, + self.pattern, value, ) def __repr__(self): - return "".format( - regex=self.regex + return "".format( + pattern=self.pattern ) @@ -99,48 +166,51 @@ def matches_re(regex, flags=0, func=None): A validator that raises `ValueError` if the initializer is called with a string that doesn't match *regex*. - :param str regex: a regex string to match against + :param regex: a regex string or precompiled pattern to match against :param int flags: flags that will be passed to the underlying re function (default 0) - :param callable func: which underlying `re` function to call (options - are `re.fullmatch`, `re.search`, `re.match`, default - is ``None`` which means either `re.fullmatch` or an emulation of - it on Python 2). For performance reasons, they won't be used directly - but on a pre-`re.compile`\ ed pattern. + :param callable func: which underlying `re` function to call. Valid options + are `re.fullmatch`, `re.search`, and `re.match`; the default ``None`` + means `re.fullmatch`. For performance reasons, the pattern is always + precompiled using `re.compile`. .. versionadded:: 19.2.0 + .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. """ - fullmatch = getattr(re, "fullmatch", None) - valid_funcs = (fullmatch, None, re.search, re.match) + valid_funcs = (re.fullmatch, None, re.search, re.match) if func not in valid_funcs: raise ValueError( - "'func' must be one of %s." - % ( + "'func' must be one of {}.".format( ", ".join( sorted( e and e.__name__ or "None" for e in set(valid_funcs) ) - ), + ) ) ) - pattern = re.compile(regex, flags) + if isinstance(regex, Pattern): + if flags: + raise TypeError( + "'flags' can only be used with a string pattern; " + "pass flags to re.compile() instead" + ) + pattern = regex + else: + pattern = re.compile(regex, flags) + if func is re.match: match_func = pattern.match elif func is re.search: match_func = pattern.search else: - if fullmatch: - match_func = pattern.fullmatch - else: - pattern = re.compile(r"(?:{})\Z".format(regex), flags) - match_func = pattern.match + match_func = pattern.fullmatch - return _MatchesReValidator(pattern, flags, match_func) + return _MatchesReValidator(pattern, match_func) @attrs(repr=False, slots=True, hash=True) -class _ProvidesValidator(object): +class _ProvidesValidator: interface = attrib() def __call__(self, inst, attr, value): @@ -175,14 +245,14 @@ def provides(interface): :type interface: ``zope.interface.Interface`` :raises TypeError: With a human readable error message, the attribute - (of type `attr.Attribute`), the expected interface, and the + (of type `attrs.Attribute`), the expected interface, and the value it got. """ return _ProvidesValidator(interface) @attrs(repr=False, slots=True, hash=True) -class _OptionalValidator(object): +class _OptionalValidator: validator = attrib() def __call__(self, inst, attr, value): @@ -216,7 +286,7 @@ def optional(validator): @attrs(repr=False, slots=True, hash=True) -class _InValidator(object): +class _InValidator: options = attrib() def __call__(self, inst, attr, value): @@ -229,7 +299,10 @@ def __call__(self, inst, attr, value): raise ValueError( "'{name}' must be in {options!r} (got {value!r})".format( name=attr.name, options=self.options, value=value - ) + ), + attr, + self.options, + value, ) def __repr__(self): @@ -248,16 +321,20 @@ def in_(options): :type options: list, tuple, `enum.Enum`, ... :raises ValueError: With a human readable error message, the attribute (of - type `attr.Attribute`), the expected options, and the value it + type `attrs.Attribute`), the expected options, and the value it got. .. versionadded:: 17.1.0 + .. versionchanged:: 22.1.0 + The ValueError was incomplete until now and only contained the human + readable error message. Now it contains all the information that has + been promised since 17.1.0. """ return _InValidator(options) @attrs(repr=False, slots=False, hash=True) -class _IsCallableValidator(object): +class _IsCallableValidator: def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. @@ -287,14 +364,14 @@ def is_callable(): .. versionadded:: 19.1.0 :raises `attr.exceptions.NotCallableError`: With a human readable error - message containing the attribute (`attr.Attribute`) name, + message containing the attribute (`attrs.Attribute`) name, and the value it got. """ return _IsCallableValidator() @attrs(repr=False, slots=True, hash=True) -class _DeepIterable(object): +class _DeepIterable: member_validator = attrib(validator=is_callable()) iterable_validator = attrib( default=None, validator=optional(is_callable()) @@ -329,7 +406,7 @@ def deep_iterable(member_validator, iterable_validator=None): """ A validator that performs deep validation of an iterable. - :param member_validator: Validator to apply to iterable members + :param member_validator: Validator(s) to apply to iterable members :param iterable_validator: Validator to apply to iterable itself (optional) @@ -337,11 +414,13 @@ def deep_iterable(member_validator, iterable_validator=None): :raises TypeError: if any sub-validators fail """ + if isinstance(member_validator, (list, tuple)): + member_validator = and_(*member_validator) return _DeepIterable(member_validator, iterable_validator) @attrs(repr=False, slots=True, hash=True) -class _DeepMapping(object): +class _DeepMapping: key_validator = attrib(validator=is_callable()) value_validator = attrib(validator=is_callable()) mapping_validator = attrib(default=None, validator=optional(is_callable())) @@ -377,3 +456,139 @@ def deep_mapping(key_validator, value_validator, mapping_validator=None): :raises TypeError: if any sub-validators fail """ return _DeepMapping(key_validator, value_validator, mapping_validator) + + +@attrs(repr=False, frozen=True, slots=True) +class _NumberValidator: + bound = attrib() + compare_op = attrib() + compare_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.compare_func(value, self.bound): + raise ValueError( + "'{name}' must be {op} {bound}: {value}".format( + name=attr.name, + op=self.compare_op, + bound=self.bound, + value=value, + ) + ) + + def __repr__(self): + return "".format( + op=self.compare_op, bound=self.bound + ) + + +def lt(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number larger or equal to *val*. + + :param val: Exclusive upper bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<", operator.lt) + + +def le(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number greater than *val*. + + :param val: Inclusive upper bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<=", operator.le) + + +def ge(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number smaller than *val*. + + :param val: Inclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">=", operator.ge) + + +def gt(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number smaller or equal to *val*. + + :param val: Exclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">", operator.gt) + + +@attrs(repr=False, frozen=True, slots=True) +class _MaxLengthValidator: + max_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) > self.max_length: + raise ValueError( + "Length of '{name}' must be <= {max}: {len}".format( + name=attr.name, max=self.max_length, len=len(value) + ) + ) + + def __repr__(self): + return "".format(max=self.max_length) + + +def max_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is longer than *length*. + + :param int length: Maximum length of the string or iterable + + .. versionadded:: 21.3.0 + """ + return _MaxLengthValidator(length) + + +@attrs(repr=False, frozen=True, slots=True) +class _MinLengthValidator: + min_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) < self.min_length: + raise ValueError( + "Length of '{name}' must be => {min}: {len}".format( + name=attr.name, min=self.min_length, len=len(value) + ) + ) + + def __repr__(self): + return "".format(min=self.min_length) + + +def min_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is shorter than *length*. + + :param int length: Minimum length of the string or iterable + + .. versionadded:: 22.1.0 + """ + return _MinLengthValidator(length) diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs.LICENSE b/conda_lock/_vendor/poetry/core/_vendor/attrs/LICENSE similarity index 94% rename from conda_lock/_vendor/poetry/core/_vendor/attrs.LICENSE rename to conda_lock/_vendor/poetry/core/_vendor/attrs/LICENSE index 7ae3df930..2bd6453d2 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/attrs.LICENSE +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Hynek Schlawack +Copyright (c) 2015 Hynek Schlawack and the attrs contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/__init__.py new file mode 100644 index 000000000..a704b8b56 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/__init__.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MIT + +from attr import ( + NOTHING, + Attribute, + Factory, + __author__, + __copyright__, + __description__, + __doc__, + __email__, + __license__, + __title__, + __url__, + __version__, + __version_info__, + assoc, + cmp_using, + define, + evolve, + field, + fields, + fields_dict, + frozen, + has, + make_class, + mutable, + resolve_types, + validate, +) +from attr._next_gen import asdict, astuple + +from . import converters, exceptions, filters, setters, validators + + +__all__ = [ + "__author__", + "__copyright__", + "__description__", + "__doc__", + "__email__", + "__license__", + "__title__", + "__url__", + "__version__", + "__version_info__", + "asdict", + "assoc", + "astuple", + "Attribute", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "Factory", + "field", + "fields_dict", + "fields", + "filters", + "frozen", + "has", + "make_class", + "mutable", + "NOTHING", + "resolve_types", + "setters", + "validate", + "validators", +] diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs/converters.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/converters.py new file mode 100644 index 000000000..edfa8d3c1 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/converters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.converters import * # noqa diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs/exceptions.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/exceptions.py new file mode 100644 index 000000000..bd9efed20 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/exceptions.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.exceptions import * # noqa diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs/filters.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/filters.py new file mode 100644 index 000000000..52959005b --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/filters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.filters import * # noqa diff --git a/conda_lock/_vendor/poetry/io/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/py.typed similarity index 100% rename from conda_lock/_vendor/poetry/io/__init__.py rename to conda_lock/_vendor/poetry/core/_vendor/attrs/py.typed diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs/setters.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/setters.py new file mode 100644 index 000000000..9b5077080 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/setters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.setters import * # noqa diff --git a/conda_lock/_vendor/poetry/core/_vendor/attrs/validators.py b/conda_lock/_vendor/poetry/core/_vendor/attrs/validators.py new file mode 100644 index 000000000..ab2c9b302 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/attrs/validators.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.validators import * # noqa diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__init__.py index 1791fe7fb..6628fc7eb 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__init__.py @@ -4,28 +4,68 @@ The main functionality is provided by the validator classes for each of the supported JSON Schema versions. -Most commonly, `validate` is the quickest way to simply validate a given -instance under a schema, and will create a validator for you. +Most commonly, `jsonschema.validators.validate` is the quickest way to simply +validate a given instance under a schema, and will create a validator +for you. """ +import warnings +from jsonschema._format import FormatChecker +from jsonschema._types import TypeChecker from jsonschema.exceptions import ( - ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError -) -from jsonschema._format import ( - FormatChecker, - draft3_format_checker, - draft4_format_checker, - draft6_format_checker, - draft7_format_checker, + ErrorTree, + FormatError, + RefResolutionError, + SchemaError, + ValidationError, ) -from jsonschema._types import TypeChecker +from jsonschema.protocols import Validator from jsonschema.validators import ( Draft3Validator, Draft4Validator, Draft6Validator, Draft7Validator, + Draft201909Validator, + Draft202012Validator, RefResolver, validate, ) -__version__ = "3.2.0" + +def __getattr__(name): + if name == "__version__": + warnings.warn( + "Accessing jsonschema.__version__ is deprecated and will be " + "removed in a future release. Use importlib.metadata directly " + "to query for jsonschema's version.", + DeprecationWarning, + stacklevel=2, + ) + + try: + from importlib import metadata + except ImportError: + import importlib_metadata as metadata + + return metadata.version("jsonschema") + + format_checkers = { + "draft3_format_checker": Draft3Validator, + "draft4_format_checker": Draft4Validator, + "draft6_format_checker": Draft6Validator, + "draft7_format_checker": Draft7Validator, + "draft201909_format_checker": Draft201909Validator, + "draft202012_format_checker": Draft202012Validator, + } + ValidatorForFormat = format_checkers.get(name) + if ValidatorForFormat is not None: + warnings.warn( + f"Accessing jsonschema.{name} is deprecated and will be " + "removed in a future release. Instead, use the FORMAT_CHECKER " + "attribute on the corresponding Validator.", + DeprecationWarning, + stacklevel=2, + ) + return ValidatorForFormat.FORMAT_CHECKER + + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__main__.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__main__.py index 82c29fd39..fdc21e230 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__main__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/__main__.py @@ -1,2 +1,3 @@ from jsonschema.cli import main + main() diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_format.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_format.py index 281a7cfcf..6a254617b 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_format.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_format.py @@ -1,13 +1,23 @@ +from __future__ import annotations + +from contextlib import suppress +from uuid import UUID import datetime +import ipaddress import re -import socket -import struct +import typing +import warnings -from jsonschema.compat import str_types from jsonschema.exceptions import FormatError +_FormatCheckCallable = typing.Callable[[object], bool] +_F = typing.TypeVar("_F", bound=_FormatCheckCallable) +_RaisesType = typing.Union[ + typing.Type[Exception], typing.Tuple[typing.Type[Exception], ...], +] + -class FormatChecker(object): +class FormatChecker: """ A ``format`` property checker. @@ -18,40 +28,43 @@ class FormatChecker(object): `FormatChecker` objects always return ``True`` when asked about formats that they do not know how to validate. - To check a custom format using a function that takes an instance and - returns a ``bool``, use the `FormatChecker.checks` or - `FormatChecker.cls_checks` decorators. + To add a check for a custom format use the `FormatChecker.checks` + decorator. Arguments: - formats (~collections.Iterable): + formats: The known formats to validate. This argument can be used to limit which formats will be used during validation. """ - checkers = {} + checkers: dict[ + str, + tuple[_FormatCheckCallable, _RaisesType], + ] = {} - def __init__(self, formats=None): + def __init__(self, formats: typing.Iterable[str] = None): if formats is None: - self.checkers = self.checkers.copy() - else: - self.checkers = dict((k, self.checkers[k]) for k in formats) + formats = self.checkers.keys() + self.checkers = {k: self.checkers[k] for k in formats} def __repr__(self): return "".format(sorted(self.checkers)) - def checks(self, format, raises=()): + def checks( + self, format: str, raises: _RaisesType = (), + ) -> typing.Callable[[_F], _F]: """ Register a decorated function as validating a new format. Arguments: - format (str): + format: The format that the decorated function will check. - raises (Exception): + raises: The exception(s) raised by the decorated function when an invalid instance is found. @@ -61,14 +74,38 @@ def checks(self, format, raises=()): resulting validation error. """ - def _checks(func): + def _checks(func: _F) -> _F: self.checkers[format] = (func, raises) return func + return _checks - cls_checks = classmethod(checks) + @classmethod + def cls_checks( + cls, format: str, raises: _RaisesType = (), + ) -> typing.Callable[[_F], _F]: + warnings.warn( + ( + "FormatChecker.cls_checks is deprecated. Call " + "FormatChecker.checks on a specific FormatChecker instance " + "instead." + ), + DeprecationWarning, + stacklevel=2, + ) + return cls._cls_checks(format=format, raises=raises) + + @classmethod + def _cls_checks( + cls, format: str, raises: _RaisesType = (), + ) -> typing.Callable[[_F], _F]: + def _checks(func: _F) -> _F: + cls.checkers[format] = (func, raises) + return func + + return _checks - def check(self, instance, format): + def check(self, instance: object, format: str) -> None: """ Check whether the instance conforms to the given format. @@ -78,14 +115,15 @@ def check(self, instance, format): The instance to check - format (str): + format: The format that instance should conform to - Raises: - FormatError: if the instance does not conform to ``format`` + FormatError: + + if the instance does not conform to ``format`` """ if format not in self.checkers: @@ -98,11 +136,9 @@ def check(self, instance, format): except raises as e: cause = e if not result: - raise FormatError( - "%r is not a %r" % (instance, format), cause=cause, - ) + raise FormatError(f"{instance!r} is not a {format!r}", cause=cause) - def conforms(self, instance, format): + def conforms(self, instance: object, format: str) -> bool: """ Check whether the instance conforms to the given format. @@ -112,7 +148,7 @@ def conforms(self, instance, format): The instance to check - format (str): + format: The format that instance should conform to @@ -133,13 +169,16 @@ def conforms(self, instance, format): draft4_format_checker = FormatChecker() draft6_format_checker = FormatChecker() draft7_format_checker = FormatChecker() +draft201909_format_checker = FormatChecker() +draft202012_format_checker = FormatChecker() - -_draft_checkers = dict( +_draft_checkers: dict[str, FormatChecker] = dict( draft3=draft3_format_checker, draft4=draft4_format_checker, draft6=draft6_format_checker, draft7=draft7_format_checker, + draft201909=draft201909_format_checker, + draft202012=draft202012_format_checker, ) @@ -149,14 +188,18 @@ def _checks_drafts( draft4=None, draft6=None, draft7=None, + draft201909=None, + draft202012=None, raises=(), -): +) -> typing.Callable[[_F], _F]: draft3 = draft3 or name draft4 = draft4 or name draft6 = draft6 or name draft7 = draft7 or name + draft201909 = draft201909 or name + draft202012 = draft202012 or name - def wrap(func): + def wrap(func: _F) -> _F: if draft3: func = _draft_checkers["draft3"].checks(draft3, raises)(func) if draft4: @@ -165,81 +208,86 @@ def wrap(func): func = _draft_checkers["draft6"].checks(draft6, raises)(func) if draft7: func = _draft_checkers["draft7"].checks(draft7, raises)(func) + if draft201909: + func = _draft_checkers["draft201909"].checks(draft201909, raises)( + func, + ) + if draft202012: + func = _draft_checkers["draft202012"].checks(draft202012, raises)( + func, + ) # Oy. This is bad global state, but relied upon for now, until - # deprecation. See https://github.com/Julian/jsonschema/issues/519 - # and test_format_checkers_come_with_defaults - FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)( - func, - ) + # deprecation. See #519 and test_format_checkers_come_with_defaults + FormatChecker._cls_checks( + draft202012 or draft201909 or draft7 or draft6 or draft4 or draft3, + raises, + )(func) return func + return wrap @_checks_drafts(name="idn-email") @_checks_drafts(name="email") -def is_email(instance): - if not isinstance(instance, str_types): +def is_email(instance: object) -> bool: + if not isinstance(instance, str): return True return "@" in instance -_ipv4_re = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") - - @_checks_drafts( - draft3="ip-address", draft4="ipv4", draft6="ipv4", draft7="ipv4", + draft3="ip-address", + draft4="ipv4", + draft6="ipv4", + draft7="ipv4", + draft201909="ipv4", + draft202012="ipv4", + raises=ipaddress.AddressValueError, ) -def is_ipv4(instance): - if not isinstance(instance, str_types): +def is_ipv4(instance: object) -> bool: + if not isinstance(instance, str): return True - if not _ipv4_re.match(instance): - return False - return all(0 <= int(component) <= 255 for component in instance.split(".")) + return bool(ipaddress.IPv4Address(instance)) -if hasattr(socket, "inet_pton"): - # FIXME: Really this only should raise struct.error, but see the sadness - # that is https://twistedmatrix.com/trac/ticket/9409 - @_checks_drafts( - name="ipv6", raises=(socket.error, struct.error, ValueError), - ) - def is_ipv6(instance): - if not isinstance(instance, str_types): - return True - return socket.inet_pton(socket.AF_INET6, instance) - +@_checks_drafts(name="ipv6", raises=ipaddress.AddressValueError) +def is_ipv6(instance: object) -> bool: + if not isinstance(instance, str): + return True + address = ipaddress.IPv6Address(instance) + return not getattr(address, "scope_id", "") -_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$") +with suppress(ImportError): + from fqdn import FQDN -@_checks_drafts( - draft3="host-name", - draft4="hostname", - draft6="hostname", - draft7="hostname", -) -def is_host_name(instance): - if not isinstance(instance, str_types): - return True - if not _host_name_re.match(instance): - return False - components = instance.split(".") - for component in components: - if len(component) > 63: - return False - return True + @_checks_drafts( + draft3="host-name", + draft4="hostname", + draft6="hostname", + draft7="hostname", + draft201909="hostname", + draft202012="hostname", + ) + def is_host_name(instance: object) -> bool: + if not isinstance(instance, str): + return True + return FQDN(instance).is_valid -try: +with suppress(ImportError): # The built-in `idna` codec only implements RFC 3890, so we go elsewhere. import idna -except ImportError: - pass -else: - @_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError) - def is_idn_host_name(instance): - if not isinstance(instance, str_types): + + @_checks_drafts( + draft7="idn-hostname", + draft201909="idn-hostname", + draft202012="idn-hostname", + raises=(idna.IDNAError, UnicodeError), + ) + def is_idn_host_name(instance: object) -> bool: + if not isinstance(instance, str): return True idna.encode(instance) return True @@ -248,137 +296,148 @@ def is_idn_host_name(instance): try: import rfc3987 except ImportError: - try: + with suppress(ImportError): from rfc3986_validator import validate_rfc3986 - except ImportError: - pass - else: + @_checks_drafts(name="uri") - def is_uri(instance): - if not isinstance(instance, str_types): + def is_uri(instance: object) -> bool: + if not isinstance(instance, str): return True return validate_rfc3986(instance, rule="URI") @_checks_drafts( draft6="uri-reference", draft7="uri-reference", + draft201909="uri-reference", + draft202012="uri-reference", raises=ValueError, ) - def is_uri_reference(instance): - if not isinstance(instance, str_types): + def is_uri_reference(instance: object) -> bool: + if not isinstance(instance, str): return True return validate_rfc3986(instance, rule="URI_reference") else: - @_checks_drafts(draft7="iri", raises=ValueError) - def is_iri(instance): - if not isinstance(instance, str_types): + + @_checks_drafts( + draft7="iri", + draft201909="iri", + draft202012="iri", + raises=ValueError, + ) + def is_iri(instance: object) -> bool: + if not isinstance(instance, str): return True return rfc3987.parse(instance, rule="IRI") - @_checks_drafts(draft7="iri-reference", raises=ValueError) - def is_iri_reference(instance): - if not isinstance(instance, str_types): + @_checks_drafts( + draft7="iri-reference", + draft201909="iri-reference", + draft202012="iri-reference", + raises=ValueError, + ) + def is_iri_reference(instance: object) -> bool: + if not isinstance(instance, str): return True return rfc3987.parse(instance, rule="IRI_reference") @_checks_drafts(name="uri", raises=ValueError) - def is_uri(instance): - if not isinstance(instance, str_types): + def is_uri(instance: object) -> bool: + if not isinstance(instance, str): return True return rfc3987.parse(instance, rule="URI") @_checks_drafts( draft6="uri-reference", draft7="uri-reference", + draft201909="uri-reference", + draft202012="uri-reference", raises=ValueError, ) - def is_uri_reference(instance): - if not isinstance(instance, str_types): + def is_uri_reference(instance: object) -> bool: + if not isinstance(instance, str): return True return rfc3987.parse(instance, rule="URI_reference") -try: - from strict_rfc3339 import validate_rfc3339 -except ImportError: - try: - from rfc3339_validator import validate_rfc3339 - except ImportError: - validate_rfc3339 = None +with suppress(ImportError): + from rfc3339_validator import validate_rfc3339 -if validate_rfc3339: @_checks_drafts(name="date-time") - def is_datetime(instance): - if not isinstance(instance, str_types): + def is_datetime(instance: object) -> bool: + if not isinstance(instance, str): return True - return validate_rfc3339(instance) + return validate_rfc3339(instance.upper()) - @_checks_drafts(draft7="time") - def is_time(instance): - if not isinstance(instance, str_types): + @_checks_drafts( + draft7="time", + draft201909="time", + draft202012="time", + ) + def is_time(instance: object) -> bool: + if not isinstance(instance, str): return True return is_datetime("1970-01-01T" + instance) @_checks_drafts(name="regex", raises=re.error) -def is_regex(instance): - if not isinstance(instance, str_types): +def is_regex(instance: object) -> bool: + if not isinstance(instance, str): return True - return re.compile(instance) + return bool(re.compile(instance)) -@_checks_drafts(draft3="date", draft7="date", raises=ValueError) -def is_date(instance): - if not isinstance(instance, str_types): +@_checks_drafts( + draft3="date", + draft7="date", + draft201909="date", + draft202012="date", + raises=ValueError, +) +def is_date(instance: object) -> bool: + if not isinstance(instance, str): return True - return datetime.datetime.strptime(instance, "%Y-%m-%d") + return bool(instance.isascii() and datetime.date.fromisoformat(instance)) @_checks_drafts(draft3="time", raises=ValueError) -def is_draft3_time(instance): - if not isinstance(instance, str_types): +def is_draft3_time(instance: object) -> bool: + if not isinstance(instance, str): return True - return datetime.datetime.strptime(instance, "%H:%M:%S") + return bool(datetime.datetime.strptime(instance, "%H:%M:%S")) -try: +with suppress(ImportError): + from webcolors import CSS21_NAMES_TO_HEX import webcolors -except ImportError: - pass -else: - def is_css_color_code(instance): + + def is_css_color_code(instance: object) -> bool: return webcolors.normalize_hex(instance) @_checks_drafts(draft3="color", raises=(ValueError, TypeError)) - def is_css21_color(instance): + def is_css21_color(instance: object) -> bool: if ( - not isinstance(instance, str_types) or - instance.lower() in webcolors.css21_names_to_hex + not isinstance(instance, str) + or instance.lower() in CSS21_NAMES_TO_HEX ): return True return is_css_color_code(instance) - def is_css3_color(instance): - if instance.lower() in webcolors.css3_names_to_hex: - return True - return is_css_color_code(instance) - -try: +with suppress(ImportError): import jsonpointer -except ImportError: - pass -else: + @_checks_drafts( draft6="json-pointer", draft7="json-pointer", + draft201909="json-pointer", + draft202012="json-pointer", raises=jsonpointer.JsonPointerException, ) - def is_json_pointer(instance): - if not isinstance(instance, str_types): + def is_json_pointer(instance: object) -> bool: + if not isinstance(instance, str): return True - return jsonpointer.JsonPointer(instance) + return bool(jsonpointer.JsonPointer(instance)) # TODO: I don't want to maintain this, so it # needs to go either into jsonpointer (pending @@ -386,16 +445,22 @@ def is_json_pointer(instance): # into a new external library. @_checks_drafts( draft7="relative-json-pointer", + draft201909="relative-json-pointer", + draft202012="relative-json-pointer", raises=jsonpointer.JsonPointerException, ) - def is_relative_json_pointer(instance): + def is_relative_json_pointer(instance: object) -> bool: # Definition taken from: # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3 - if not isinstance(instance, str_types): + if not isinstance(instance, str): return True non_negative_integer, rest = [], "" for i, character in enumerate(instance): if character.isdigit(): + # digits with a leading "0" are not allowed + if i > 0 and int(instance[i - 1]) == 0: + return False + non_negative_integer.append(character) continue @@ -404,22 +469,45 @@ def is_relative_json_pointer(instance): rest = instance[i:] break - return (rest == "#") or jsonpointer.JsonPointer(rest) + return (rest == "#") or bool(jsonpointer.JsonPointer(rest)) -try: - import uritemplate.exceptions -except ImportError: - pass -else: +with suppress(ImportError): + import uri_template + @_checks_drafts( draft6="uri-template", draft7="uri-template", - raises=uritemplate.exceptions.InvalidTemplate, + draft201909="uri-template", + draft202012="uri-template", ) - def is_uri_template( - instance, - template_validator=uritemplate.Validator().force_balanced_braces(), - ): - template = uritemplate.URITemplate(instance) - return template_validator.validate(template) + def is_uri_template(instance: object) -> bool: + if not isinstance(instance, str): + return True + return uri_template.validate(instance) + + +with suppress(ImportError): + import isoduration + + @_checks_drafts( + draft201909="duration", + draft202012="duration", + raises=isoduration.DurationParsingException, + ) + def is_duration(instance: object) -> bool: + if not isinstance(instance, str): + return True + return bool(isoduration.parse_duration(instance)) + + +@_checks_drafts( + draft201909="uuid", + draft202012="uuid", + raises=ValueError, +) +def is_uuid(instance: object) -> bool: + if not isinstance(instance, str): + return True + UUID(instance) + return all(instance[position] == "-" for position in (8, 13, 18, 23)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_legacy_validators.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_legacy_validators.py index 264ff7d71..cc5e3f44c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_legacy_validators.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_legacy_validators.py @@ -1,49 +1,101 @@ from jsonschema import _utils -from jsonschema.compat import iteritems from jsonschema.exceptions import ValidationError +def id_of_ignore_ref(property="$id"): + def id_of(schema): + """ + Ignore an ``$id`` sibling of ``$ref`` if it is present. + + Otherwise, return the ID of the given schema. + """ + if schema is True or schema is False or "$ref" in schema: + return "" + return schema.get(property, "") + return id_of + + +def ignore_ref_siblings(schema): + """ + Ignore siblings of ``$ref`` if it is present. + + Otherwise, return all keywords. + + Suitable for use with `create`'s ``applicable_validators`` argument. + """ + ref = schema.get("$ref") + if ref is not None: + return [("$ref", ref)] + else: + return schema.items() + + def dependencies_draft3(validator, dependencies, instance, schema): if not validator.is_type(instance, "object"): return - for property, dependency in iteritems(dependencies): + for property, dependency in dependencies.items(): if property not in instance: continue if validator.is_type(dependency, "object"): - for error in validator.descend( + yield from validator.descend( instance, dependency, schema_path=property, - ): - yield error + ) elif validator.is_type(dependency, "string"): if dependency not in instance: - yield ValidationError( - "%r is a dependency of %r" % (dependency, property) - ) + message = f"{dependency!r} is a dependency of {property!r}" + yield ValidationError(message) else: for each in dependency: if each not in instance: - message = "%r is a dependency of %r" - yield ValidationError(message % (each, property)) + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) + + +def dependencies_draft4_draft6_draft7( + validator, + dependencies, + instance, + schema, +): + """ + Support for the ``dependencies`` keyword from pre-draft 2019-09. + + In later drafts, the keyword was split into separate + ``dependentRequired`` and ``dependentSchemas`` validators. + """ + if not validator.is_type(instance, "object"): + return + + for property, dependency in dependencies.items(): + if property not in instance: + continue + + if validator.is_type(dependency, "array"): + for each in dependency: + if each not in instance: + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) + else: + yield from validator.descend( + instance, dependency, schema_path=property, + ) def disallow_draft3(validator, disallow, instance, schema): for disallowed in _utils.ensure_list(disallow): - if validator.is_valid(instance, {"type": [disallowed]}): - yield ValidationError( - "%r is disallowed for %r" % (disallowed, instance) - ) + if validator.evolve(schema={"type": [disallowed]}).is_valid(instance): + message = f"{disallowed!r} is disallowed for {instance!r}" + yield ValidationError(message) def extends_draft3(validator, extends, instance, schema): if validator.is_type(extends, "object"): - for error in validator.descend(instance, extends): - yield error + yield from validator.descend(instance, extends) return for index, subschema in enumerate(extends): - for error in validator.descend(instance, subschema, schema_path=index): - yield error + yield from validator.descend(instance, subschema, schema_path=index) def items_draft3_draft4(validator, items, instance, schema): @@ -52,14 +104,26 @@ def items_draft3_draft4(validator, items, instance, schema): if validator.is_type(items, "object"): for index, item in enumerate(instance): - for error in validator.descend(item, items, path=index): - yield error + yield from validator.descend(item, items, path=index) else: for (index, item), subschema in zip(enumerate(instance), items): - for error in validator.descend( + yield from validator.descend( item, subschema, path=index, schema_path=index, - ): - yield error + ) + + +def items_draft6_draft7_draft201909(validator, items, instance, schema): + if not validator.is_type(instance, "array"): + return + + if validator.is_type(items, "array"): + for (index, item), subschema in zip(enumerate(instance), items): + yield from validator.descend( + item, subschema, path=index, schema_path=index, + ) + else: + for index, item in enumerate(instance): + yield from validator.descend(item, items, path=index) def minimum_draft3_draft4(validator, minimum, instance, schema): @@ -74,9 +138,8 @@ def minimum_draft3_draft4(validator, minimum, instance, schema): cmp = "less than" if failed: - yield ValidationError( - "%r is %s the minimum of %r" % (instance, cmp, minimum) - ) + message = f"{instance!r} is {cmp} the minimum of {minimum!r}" + yield ValidationError(message) def maximum_draft3_draft4(validator, maximum, instance, schema): @@ -91,26 +154,24 @@ def maximum_draft3_draft4(validator, maximum, instance, schema): cmp = "greater than" if failed: - yield ValidationError( - "%r is %s the maximum of %r" % (instance, cmp, maximum) - ) + message = f"{instance!r} is {cmp} the maximum of {maximum!r}" + yield ValidationError(message) def properties_draft3(validator, properties, instance, schema): if not validator.is_type(instance, "object"): return - for property, subschema in iteritems(properties): + for property, subschema in properties.items(): if property in instance: - for error in validator.descend( + yield from validator.descend( instance[property], subschema, path=property, schema_path=property, - ): - yield error + ) elif subschema.get("required", False): - error = ValidationError("%r is a required property" % property) + error = ValidationError(f"{property!r} is a required property") error._set( validator="required", validator_value=subschema["required"], @@ -136,6 +197,123 @@ def type_draft3(validator, types, instance, schema): if validator.is_type(instance, type): return else: + reprs = [] + for type in types: + try: + reprs.append(repr(type["name"])) + except Exception: + reprs.append(repr(type)) yield ValidationError( - _utils.types_msg(instance, types), context=all_errors, + f"{instance!r} is not of type {', '.join(reprs)}", + context=all_errors, ) + + +def contains_draft6_draft7(validator, contains, instance, schema): + if not validator.is_type(instance, "array"): + return + + if not any( + validator.evolve(schema=contains).is_valid(element) + for element in instance + ): + yield ValidationError( + f"None of {instance!r} are valid under the given schema", + ) + + +def recursiveRef(validator, recursiveRef, instance, schema): + lookup_url, target = validator.resolver.resolution_scope, validator.schema + + for each in reversed(validator.resolver._scopes_stack[1:]): + lookup_url, next_target = validator.resolver.resolve(each) + if next_target.get("$recursiveAnchor"): + target = next_target + else: + break + + fragment = recursiveRef.lstrip("#") + subschema = validator.resolver.resolve_fragment(target, fragment) + # FIXME: This is gutted (and not calling .descend) because it can trigger + # recursion errors, so there's a bug here. Re-enable the tests to + # see it. + subschema + return [] + + +def find_evaluated_item_indexes_by_schema(validator, instance, schema): + """ + Get all indexes of items that get evaluated under the current schema + + Covers all keywords related to unevaluatedItems: items, prefixItems, if, + then, else, contains, unevaluatedItems, allOf, oneOf, anyOf + """ + if validator.is_type(schema, "boolean"): + return [] + evaluated_indexes = [] + + if "additionalItems" in schema: + return list(range(0, len(instance))) + + if "$ref" in schema: + scope, resolved = validator.resolver.resolve(schema["$ref"]) + validator.resolver.push_scope(scope) + + try: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, resolved, + ) + finally: + validator.resolver.pop_scope() + + if "items" in schema: + if validator.is_type(schema["items"], "object"): + return list(range(0, len(instance))) + evaluated_indexes += list(range(0, len(schema["items"]))) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["then"], + ) + else: + if "else" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["else"], + ) + + for keyword in ["contains", "unevaluatedItems"]: + if keyword in schema: + for k, v in enumerate(instance): + if validator.evolve(schema=schema[keyword]).is_valid(v): + evaluated_indexes.append(k) + + for keyword in ["allOf", "oneOf", "anyOf"]: + if keyword in schema: + for subschema in schema[keyword]: + errs = list(validator.descend(instance, subschema)) + if not errs: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, subschema, + ) + + return evaluated_indexes + + +def unevaluatedItems_draft2019(validator, unevaluatedItems, instance, schema): + if not validator.is_type(instance, "array"): + return + evaluated_item_indexes = find_evaluated_item_indexes_by_schema( + validator, instance, schema, + ) + unevaluated_items = [ + item for index, item in enumerate(instance) + if index not in evaluated_item_indexes + ] + if unevaluated_items: + error = "Unevaluated items are not allowed (%s %s unexpected)" + yield ValidationError(error % _utils.extras_msg(unevaluated_items)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_reflect.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_reflect.py deleted file mode 100644 index d09e38fbd..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_reflect.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- test-case-name: twisted.test.test_reflect -*- -# Copyright (c) Twisted Matrix Laboratories. -# See LICENSE for details. - -""" -Standardized versions of various cool and/or strange things that you can do -with Python's reflection capabilities. -""" - -import sys - -from jsonschema.compat import PY3 - - -class _NoModuleFound(Exception): - """ - No module was found because none exists. - """ - - - -class InvalidName(ValueError): - """ - The given name is not a dot-separated list of Python objects. - """ - - - -class ModuleNotFound(InvalidName): - """ - The module associated with the given name doesn't exist and it can't be - imported. - """ - - - -class ObjectNotFound(InvalidName): - """ - The object associated with the given name doesn't exist and it can't be - imported. - """ - - - -if PY3: - def reraise(exception, traceback): - raise exception.with_traceback(traceback) -else: - exec("""def reraise(exception, traceback): - raise exception.__class__, exception, traceback""") - -reraise.__doc__ = """ -Re-raise an exception, with an optional traceback, in a way that is compatible -with both Python 2 and Python 3. - -Note that on Python 3, re-raised exceptions will be mutated, with their -C{__traceback__} attribute being set. - -@param exception: The exception instance. -@param traceback: The traceback to use, or C{None} indicating a new traceback. -""" - - -def _importAndCheckStack(importName): - """ - Import the given name as a module, then walk the stack to determine whether - the failure was the module not existing, or some code in the module (for - example a dependent import) failing. This can be helpful to determine - whether any actual application code was run. For example, to distiguish - administrative error (entering the wrong module name), from programmer - error (writing buggy code in a module that fails to import). - - @param importName: The name of the module to import. - @type importName: C{str} - @raise Exception: if something bad happens. This can be any type of - exception, since nobody knows what loading some arbitrary code might - do. - @raise _NoModuleFound: if no module was found. - """ - try: - return __import__(importName) - except ImportError: - excType, excValue, excTraceback = sys.exc_info() - while excTraceback: - execName = excTraceback.tb_frame.f_globals["__name__"] - # in Python 2 execName is None when an ImportError is encountered, - # where in Python 3 execName is equal to the importName. - if execName is None or execName == importName: - reraise(excValue, excTraceback) - excTraceback = excTraceback.tb_next - raise _NoModuleFound() - - - -def namedAny(name): - """ - Retrieve a Python object by its fully qualified name from the global Python - module namespace. The first part of the name, that describes a module, - will be discovered and imported. Each subsequent part of the name is - treated as the name of an attribute of the object specified by all of the - name which came before it. For example, the fully-qualified name of this - object is 'twisted.python.reflect.namedAny'. - - @type name: L{str} - @param name: The name of the object to return. - - @raise InvalidName: If the name is an empty string, starts or ends with - a '.', or is otherwise syntactically incorrect. - - @raise ModuleNotFound: If the name is syntactically correct but the - module it specifies cannot be imported because it does not appear to - exist. - - @raise ObjectNotFound: If the name is syntactically correct, includes at - least one '.', but the module it specifies cannot be imported because - it does not appear to exist. - - @raise AttributeError: If an attribute of an object along the way cannot be - accessed, or a module along the way is not found. - - @return: the Python object identified by 'name'. - """ - if not name: - raise InvalidName('Empty module name') - - names = name.split('.') - - # if the name starts or ends with a '.' or contains '..', the __import__ - # will raise an 'Empty module name' error. This will provide a better error - # message. - if '' in names: - raise InvalidName( - "name must be a string giving a '.'-separated list of Python " - "identifiers, not %r" % (name,)) - - topLevelPackage = None - moduleNames = names[:] - while not topLevelPackage: - if moduleNames: - trialname = '.'.join(moduleNames) - try: - topLevelPackage = _importAndCheckStack(trialname) - except _NoModuleFound: - moduleNames.pop() - else: - if len(names) == 1: - raise ModuleNotFound("No module named %r" % (name,)) - else: - raise ObjectNotFound('%r does not name an object' % (name,)) - - obj = topLevelPackage - for n in names[1:]: - obj = getattr(obj, n) - - return obj diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_types.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_types.py index a71a4e34b..5b543f71b 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_types.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_types.py @@ -1,12 +1,28 @@ +from __future__ import annotations + import numbers +import typing from pyrsistent import pmap +from pyrsistent.typing import PMap import attr -from jsonschema.compat import int_types, str_types from jsonschema.exceptions import UndefinedTypeCheck +# unfortunately, the type of pmap is generic, and if used as the attr.ib +# converter, the generic type is presented to mypy, which then fails to match +# the concrete type of a type checker mapping +# this "do nothing" wrapper presents the correct information to mypy +def _typed_pmap_converter( + init_val: typing.Mapping[ + str, + typing.Callable[["TypeChecker", typing.Any], bool], + ], +) -> PMap[str, typing.Callable[["TypeChecker", typing.Any], bool]]: + return pmap(init_val) + + def is_array(checker, instance): return isinstance(instance, list) @@ -19,7 +35,7 @@ def is_integer(checker, instance): # bool inherits from int, so ensure bools aren't reported as ints if isinstance(instance, bool): return False - return isinstance(instance, int_types) + return isinstance(instance, int) def is_null(checker, instance): @@ -38,86 +54,92 @@ def is_object(checker, instance): def is_string(checker, instance): - return isinstance(instance, str_types) + return isinstance(instance, str) def is_any(checker, instance): return True -@attr.s(frozen=True) -class TypeChecker(object): +@attr.s(frozen=True, repr=False) +class TypeChecker: """ - A ``type`` property checker. + A :kw:`type` property checker. + + A `TypeChecker` performs type checking for a `Validator`, converting + between the defined JSON Schema types and some associated Python types or + objects. - A `TypeChecker` performs type checking for an `IValidator`. Type - checks to perform are updated using `TypeChecker.redefine` or - `TypeChecker.redefine_many` and removed via `TypeChecker.remove`. - Each of these return a new `TypeChecker` object. + Modifying the behavior just mentioned by redefining which Python objects + are considered to be of which JSON Schema types can be done using + `TypeChecker.redefine` or `TypeChecker.redefine_many`, and types can be + removed via `TypeChecker.remove`. Each of these return a new `TypeChecker`. Arguments: - type_checkers (dict): + type_checkers: The initial mapping of types to their checking functions. """ - _type_checkers = attr.ib(default=pmap(), converter=pmap) - def is_type(self, instance, type): + _type_checkers: PMap[ + str, typing.Callable[["TypeChecker", typing.Any], bool], + ] = attr.ib( + default=pmap(), + converter=_typed_pmap_converter, + ) + + def __repr__(self): + types = ", ".join(repr(k) for k in sorted(self._type_checkers)) + return f"<{self.__class__.__name__} types={{{types}}}>" + + def is_type(self, instance, type: str) -> bool: """ Check if the instance is of the appropriate type. Arguments: - instance (object): + instance: The instance to check - type (str): + type: The name of the type that is expected. - Returns: - - bool: Whether it conformed. - - Raises: `jsonschema.exceptions.UndefinedTypeCheck`: - if type is unknown to this object. + + if ``type`` is unknown to this object. """ try: fn = self._type_checkers[type] except KeyError: - raise UndefinedTypeCheck(type) + raise UndefinedTypeCheck(type) from None return fn(self, instance) - def redefine(self, type, fn): + def redefine(self, type: str, fn) -> "TypeChecker": """ Produce a new checker with the given type redefined. Arguments: - type (str): + type: The name of the type to check. - fn (collections.Callable): + fn (collections.abc.Callable): - A function taking exactly two parameters - the type + A callable taking exactly two parameters - the type checker calling the function and the instance to check. The function should return true if instance is of this type and false otherwise. - - Returns: - - A new `TypeChecker` instance. """ return self.redefine_many({type: fn}) - def redefine_many(self, definitions=()): + def redefine_many(self, definitions=()) -> "TypeChecker": """ Produce a new checker with the given types redefined. @@ -126,29 +148,20 @@ def redefine_many(self, definitions=()): definitions (dict): A dictionary mapping types to their checking functions. - - Returns: - - A new `TypeChecker` instance. """ - return attr.evolve( - self, type_checkers=self._type_checkers.update(definitions), - ) + type_checkers = self._type_checkers.update(definitions) + return attr.evolve(self, type_checkers=type_checkers) - def remove(self, *types): + def remove(self, *types) -> "TypeChecker": """ Produce a new checker with the given types forgotten. Arguments: - types (~collections.Iterable): + types: the names of the types to remove. - Returns: - - A new `TypeChecker` instance - Raises: `jsonschema.exceptions.UndefinedTypeCheck`: @@ -156,33 +169,35 @@ def remove(self, *types): if any given type is unknown to this object """ - checkers = self._type_checkers + type_checkers = self._type_checkers for each in types: try: - checkers = checkers.remove(each) + type_checkers = type_checkers.remove(each) except KeyError: raise UndefinedTypeCheck(each) - return attr.evolve(self, type_checkers=checkers) + return attr.evolve(self, type_checkers=type_checkers) draft3_type_checker = TypeChecker( { - u"any": is_any, - u"array": is_array, - u"boolean": is_bool, - u"integer": is_integer, - u"object": is_object, - u"null": is_null, - u"number": is_number, - u"string": is_string, + "any": is_any, + "array": is_array, + "boolean": is_bool, + "integer": is_integer, + "object": is_object, + "null": is_null, + "number": is_number, + "string": is_string, }, ) -draft4_type_checker = draft3_type_checker.remove(u"any") +draft4_type_checker = draft3_type_checker.remove("any") draft6_type_checker = draft4_type_checker.redefine( - u"integer", + "integer", lambda checker, instance: ( - is_integer(checker, instance) or - isinstance(instance, float) and instance.is_integer() + is_integer(checker, instance) + or isinstance(instance, float) and instance.is_integer() ), ) draft7_type_checker = draft6_type_checker +draft201909_type_checker = draft7_type_checker +draft202012_type_checker = draft201909_type_checker diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_utils.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_utils.py index 117eec24b..a31ab4317 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_utils.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_utils.py @@ -1,11 +1,10 @@ +from collections.abc import Mapping, MutableMapping, Sequence +from urllib.parse import urlsplit import itertools import json import os import re -from jsonschema.compat import MutableMapping, str_types, urlsplit - - class URIDict(MutableMapping): """ Dictionary which uses normalized URIs as keys. @@ -37,7 +36,7 @@ def __repr__(self): return repr(self.store) -class Unset(object): +class Unset: """ An as-of-yet unset attribute or unprovided default parameter. """ @@ -51,37 +50,34 @@ def load_schema(name): Load a schema from ./schemas/``name``.json and return it. """ with open( - os.path.join(os.path.dirname(__file__), "schemas", "{0}.json".format(name)) + os.path.join(os.path.dirname(__file__), "schemas", "{0}.json".format(name)), + encoding="utf-8" ) as f: data = f.read() return json.loads(data) -def indent(string, times=1): - """ - A dumb version of `textwrap.indent` from Python 3.3. - """ - - return "\n".join(" " * (4 * times) + line for line in string.splitlines()) - - -def format_as_index(indices): +def format_as_index(container, indices): """ Construct a single string containing indexing operations for the indices. - For example, [1, 2, "foo"] -> [1][2]["foo"] + For example for a container ``bar``, [1, 2, "foo"] -> bar[1][2]["foo"] Arguments: + container (str): + + A word to use for the thing being indexed + indices (sequence): The indices to format. """ if not indices: - return "" - return "[%s]" % "][".join(repr(index) for index in indices) + return container + return f"{container}[{']['.join(repr(index) for index in indices)}]" def find_additional_properties(instance, schema): @@ -112,66 +108,55 @@ def extras_msg(extras): verb = "was" else: verb = "were" - return ", ".join(repr(extra) for extra in extras), verb + return ", ".join(repr(extra) for extra in sorted(extras)), verb -def types_msg(instance, types): +def ensure_list(thing): """ - Create an error message for a failure to match the given types. - - If the ``instance`` is an object and contains a ``name`` property, it will - be considered to be a description of that object and used as its type. + Wrap ``thing`` in a list if it's a single str. - Otherwise the message is simply the reprs of the given ``types``. + Otherwise, return it unchanged. """ - reprs = [] - for type in types: - try: - reprs.append(repr(type["name"])) - except Exception: - reprs.append(repr(type)) - return "%r is not of type %s" % (instance, ", ".join(reprs)) + if isinstance(thing, str): + return [thing] + return thing -def flatten(suitable_for_isinstance): +def _mapping_equal(one, two): """ - isinstance() can accept a bunch of really annoying different types: - * a single type - * a tuple of types - * an arbitrary nested tree of tuples - - Return a flattened tuple of the given argument. + Check if two mappings are equal using the semantics of `equal`. """ + if len(one) != len(two): + return False + return all( + key in two and equal(value, two[key]) + for key, value in one.items() + ) - types = set() - if not isinstance(suitable_for_isinstance, tuple): - suitable_for_isinstance = (suitable_for_isinstance,) - for thing in suitable_for_isinstance: - if isinstance(thing, tuple): - types.update(flatten(thing)) - else: - types.add(thing) - return tuple(types) - - -def ensure_list(thing): +def _sequence_equal(one, two): """ - Wrap ``thing`` in a list if it's a single str. - - Otherwise, return it unchanged. + Check if two sequences are equal using the semantics of `equal`. """ - - if isinstance(thing, str_types): - return [thing] - return thing + if len(one) != len(two): + return False + return all(equal(i, j) for i, j in zip(one, two)) def equal(one, two): """ - Check if two things are equal, but evade booleans and ints being equal. + Check if two things are equal evading some Python type hierarchy semantics. + + Specifically in JSON Schema, evade `bool` inheriting from `int`, + recursing into sequences to do the same. """ + if isinstance(one, str) or isinstance(two, str): + return one == two + if isinstance(one, Sequence) and isinstance(two, Sequence): + return _sequence_equal(one, two) + if isinstance(one, Mapping) and isinstance(two, Mapping): + return _mapping_equal(one, two) return unbool(one) == unbool(two) @@ -191,25 +176,170 @@ def uniq(container): """ Check if all of a container's elements are unique. - Successively tries first to rely that the elements are hashable, then - falls back on them being sortable, and finally falls back on brute - force. + Tries to rely on the container being recursively sortable, or otherwise + falls back on (slow) brute force. """ - try: - return len(set(unbool(i) for i in container)) == len(container) - except TypeError: - try: - sort = sorted(unbool(i) for i in container) - sliced = itertools.islice(sort, 1, None) - for i, j in zip(sort, sliced): - if i == j: - return False - except (NotImplementedError, TypeError): - seen = [] - for e in container: - e = unbool(e) - if e in seen: + sort = sorted(unbool(i) for i in container) + sliced = itertools.islice(sort, 1, None) + + for i, j in zip(sort, sliced): + if equal(i, j): + return False + + except (NotImplementedError, TypeError): + seen = [] + for e in container: + e = unbool(e) + + for i in seen: + if equal(i, e): return False - seen.append(e) + + seen.append(e) return True + + +def find_evaluated_item_indexes_by_schema(validator, instance, schema): + """ + Get all indexes of items that get evaluated under the current schema + + Covers all keywords related to unevaluatedItems: items, prefixItems, if, + then, else, contains, unevaluatedItems, allOf, oneOf, anyOf + """ + if validator.is_type(schema, "boolean"): + return [] + evaluated_indexes = [] + + if "items" in schema: + return list(range(0, len(instance))) + + if "$ref" in schema: + scope, resolved = validator.resolver.resolve(schema["$ref"]) + validator.resolver.push_scope(scope) + + try: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, resolved, + ) + finally: + validator.resolver.pop_scope() + + if "prefixItems" in schema: + evaluated_indexes += list(range(0, len(schema["prefixItems"]))) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["then"], + ) + else: + if "else" in schema: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, schema["else"], + ) + + for keyword in ["contains", "unevaluatedItems"]: + if keyword in schema: + for k, v in enumerate(instance): + if validator.evolve(schema=schema[keyword]).is_valid(v): + evaluated_indexes.append(k) + + for keyword in ["allOf", "oneOf", "anyOf"]: + if keyword in schema: + for subschema in schema[keyword]: + errs = list(validator.descend(instance, subschema)) + if not errs: + evaluated_indexes += find_evaluated_item_indexes_by_schema( + validator, instance, subschema, + ) + + return evaluated_indexes + + +def find_evaluated_property_keys_by_schema(validator, instance, schema): + """ + Get all keys of items that get evaluated under the current schema + + Covers all keywords related to unevaluatedProperties: properties, + additionalProperties, unevaluatedProperties, patternProperties, + dependentSchemas, allOf, oneOf, anyOf, if, then, else + """ + if validator.is_type(schema, "boolean"): + return [] + evaluated_keys = [] + + if "$ref" in schema: + scope, resolved = validator.resolver.resolve(schema["$ref"]) + validator.resolver.push_scope(scope) + + try: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, resolved, + ) + finally: + validator.resolver.pop_scope() + + for keyword in [ + "properties", "additionalProperties", "unevaluatedProperties", + ]: + if keyword in schema: + if validator.is_type(schema[keyword], "boolean"): + for property, value in instance.items(): + if validator.evolve(schema=schema[keyword]).is_valid( + {property: value}, + ): + evaluated_keys.append(property) + + if validator.is_type(schema[keyword], "object"): + for property, subschema in schema[keyword].items(): + if property in instance and validator.evolve( + schema=subschema, + ).is_valid(instance[property]): + evaluated_keys.append(property) + + if "patternProperties" in schema: + for property, value in instance.items(): + for pattern, _ in schema["patternProperties"].items(): + if re.search(pattern, property) and validator.evolve( + schema=schema["patternProperties"], + ).is_valid({property: value}): + evaluated_keys.append(property) + + if "dependentSchemas" in schema: + for property, subschema in schema["dependentSchemas"].items(): + if property not in instance: + continue + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, subschema, + ) + + for keyword in ["allOf", "oneOf", "anyOf"]: + if keyword in schema: + for subschema in schema[keyword]: + errs = list(validator.descend(instance, subschema)) + if not errs: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, subschema, + ) + + if "if" in schema: + if validator.evolve(schema=schema["if"]).is_valid(instance): + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["if"], + ) + if "then" in schema: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["then"], + ) + else: + if "else" in schema: + evaluated_keys += find_evaluated_property_keys_by_schema( + validator, instance, schema["else"], + ) + + return evaluated_keys diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_validators.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_validators.py index 179fec09a..874e8796f 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_validators.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/_validators.py @@ -1,3 +1,5 @@ +from fractions import Fraction +from urllib.parse import urldefrag, urljoin import re from jsonschema._utils import ( @@ -5,25 +7,24 @@ equal, extras_msg, find_additional_properties, - types_msg, + find_evaluated_item_indexes_by_schema, + find_evaluated_property_keys_by_schema, unbool, uniq, ) from jsonschema.exceptions import FormatError, ValidationError -from jsonschema.compat import iteritems def patternProperties(validator, patternProperties, instance, schema): if not validator.is_type(instance, "object"): return - for pattern, subschema in iteritems(patternProperties): - for k, v in iteritems(instance): + for pattern, subschema in patternProperties.items(): + for k, v in instance.items(): if re.search(pattern, k): - for error in validator.descend( + yield from validator.descend( v, subschema, path=k, schema_path=pattern, - ): - yield error + ) def propertyNames(validator, propertyNames, instance, schema): @@ -31,11 +32,7 @@ def propertyNames(validator, propertyNames, instance, schema): return for property in instance: - for error in validator.descend( - instance=property, - schema=propertyNames, - ): - yield error + yield from validator.descend(instance=property, schema=propertyNames) def additionalProperties(validator, aP, instance, schema): @@ -46,20 +43,19 @@ def additionalProperties(validator, aP, instance, schema): if validator.is_type(aP, "object"): for extra in extras: - for error in validator.descend(instance[extra], aP, path=extra): - yield error + yield from validator.descend(instance[extra], aP, path=extra) elif not aP and extras: if "patternProperties" in schema: - patterns = sorted(schema["patternProperties"]) if len(extras) == 1: verb = "does" else: verb = "do" - error = "%s %s not match any of the regexes: %s" % ( - ", ".join(map(repr, sorted(extras))), - verb, - ", ".join(map(repr, patterns)), + + joined = ", ".join(repr(each) for each in sorted(extras)) + patterns = ", ".join( + repr(each) for each in sorted(schema["patternProperties"]) ) + error = f"{joined} {verb} not match any of the regexes: {patterns}" yield ValidationError(error) else: error = "Additional properties are not allowed (%s %s unexpected)" @@ -70,51 +66,76 @@ def items(validator, items, instance, schema): if not validator.is_type(instance, "array"): return - if validator.is_type(items, "array"): - for (index, item), subschema in zip(enumerate(instance), items): - for error in validator.descend( - item, subschema, path=index, schema_path=index, - ): - yield error + prefix = len(schema.get("prefixItems", [])) + total = len(instance) + if items is False and total > prefix: + message = f"Expected at most {prefix} items, but found {total}" + yield ValidationError(message) else: - for index, item in enumerate(instance): - for error in validator.descend(item, items, path=index): - yield error + for index in range(prefix, total): + yield from validator.descend( + instance=instance[index], + schema=items, + path=index, + ) def additionalItems(validator, aI, instance, schema): if ( - not validator.is_type(instance, "array") or - validator.is_type(schema.get("items", {}), "object") + not validator.is_type(instance, "array") + or validator.is_type(schema.get("items", {}), "object") ): return len_items = len(schema.get("items", [])) if validator.is_type(aI, "object"): for index, item in enumerate(instance[len_items:], start=len_items): - for error in validator.descend(item, aI, path=index): - yield error + yield from validator.descend(item, aI, path=index) elif not aI and len(instance) > len(schema.get("items", [])): error = "Additional items are not allowed (%s %s unexpected)" yield ValidationError( - error % - extras_msg(instance[len(schema.get("items", [])):]) + error % extras_msg(instance[len(schema.get("items", [])):]), ) def const(validator, const, instance, schema): if not equal(instance, const): - yield ValidationError("%r was expected" % (const,)) + yield ValidationError(f"{const!r} was expected") def contains(validator, contains, instance, schema): if not validator.is_type(instance, "array"): return - if not any(validator.is_valid(element, contains) for element in instance): - yield ValidationError( - "None of %r are valid under the given schema" % (instance,) - ) + matches = 0 + min_contains = schema.get("minContains", 1) + max_contains = schema.get("maxContains", len(instance)) + + for each in instance: + if validator.evolve(schema=contains).is_valid(each): + matches += 1 + if matches > max_contains: + yield ValidationError( + "Too many items match the given schema " + f"(expected at most {max_contains})", + validator="maxContains", + validator_value=max_contains, + ) + return + + if matches < min_contains: + if not matches: + yield ValidationError( + f"{instance!r} does not contain items " + "matching the given schema", + ) + else: + yield ValidationError( + "Too few items match the given schema (expected at least " + f"{min_contains} but only {matches} matched)", + validator="minContains", + validator_value=min_contains, + ) def exclusiveMinimum(validator, minimum, instance, schema): @@ -123,9 +144,8 @@ def exclusiveMinimum(validator, minimum, instance, schema): if instance <= minimum: yield ValidationError( - "%r is less than or equal to the minimum of %r" % ( - instance, minimum, - ), + f"{instance!r} is less than or equal to " + f"the minimum of {minimum!r}", ) @@ -135,9 +155,8 @@ def exclusiveMaximum(validator, maximum, instance, schema): if instance >= maximum: yield ValidationError( - "%r is greater than or equal to the maximum of %r" % ( - instance, maximum, - ), + f"{instance!r} is greater than or equal " + f"to the maximum of {maximum!r}", ) @@ -146,9 +165,8 @@ def minimum(validator, minimum, instance, schema): return if instance < minimum: - yield ValidationError( - "%r is less than the minimum of %r" % (instance, minimum) - ) + message = f"{instance!r} is less than the minimum of {minimum!r}" + yield ValidationError(message) def maximum(validator, maximum, instance, schema): @@ -156,9 +174,8 @@ def maximum(validator, maximum, instance, schema): return if instance > maximum: - yield ValidationError( - "%r is greater than the maximum of %r" % (instance, maximum) - ) + message = f"{instance!r} is greater than the maximum of {maximum!r}" + yield ValidationError(message) def multipleOf(validator, dB, instance, schema): @@ -167,39 +184,52 @@ def multipleOf(validator, dB, instance, schema): if isinstance(dB, float): quotient = instance / dB - failed = int(quotient) != quotient + try: + failed = int(quotient) != quotient + except OverflowError: + # When `instance` is large and `dB` is less than one, + # quotient can overflow to infinity; and then casting to int + # raises an error. + # + # In this case we fall back to Fraction logic, which is + # exact and cannot overflow. The performance is also + # acceptable: we try the fast all-float option first, and + # we know that fraction(dB) can have at most a few hundred + # digits in each part. The worst-case slowdown is therefore + # for already-slow enormous integers or Decimals. + failed = (Fraction(instance) / Fraction(dB)).denominator != 1 else: failed = instance % dB if failed: - yield ValidationError("%r is not a multiple of %r" % (instance, dB)) + yield ValidationError(f"{instance!r} is not a multiple of {dB}") def minItems(validator, mI, instance, schema): if validator.is_type(instance, "array") and len(instance) < mI: - yield ValidationError("%r is too short" % (instance,)) + yield ValidationError(f"{instance!r} is too short") def maxItems(validator, mI, instance, schema): if validator.is_type(instance, "array") and len(instance) > mI: - yield ValidationError("%r is too long" % (instance,)) + yield ValidationError(f"{instance!r} is too long") def uniqueItems(validator, uI, instance, schema): if ( - uI and - validator.is_type(instance, "array") and - not uniq(instance) + uI + and validator.is_type(instance, "array") + and not uniq(instance) ): - yield ValidationError("%r has non-unique elements" % (instance,)) + yield ValidationError(f"{instance!r} has non-unique elements") def pattern(validator, patrn, instance, schema): if ( - validator.is_type(instance, "string") and - not re.search(patrn, instance) + validator.is_type(instance, "string") + and not re.search(patrn, instance) ): - yield ValidationError("%r does not match %r" % (instance, patrn)) + yield ValidationError(f"{instance!r} does not match {patrn!r}") def format(validator, format, instance, schema): @@ -212,80 +242,99 @@ def format(validator, format, instance, schema): def minLength(validator, mL, instance, schema): if validator.is_type(instance, "string") and len(instance) < mL: - yield ValidationError("%r is too short" % (instance,)) + yield ValidationError(f"{instance!r} is too short") def maxLength(validator, mL, instance, schema): if validator.is_type(instance, "string") and len(instance) > mL: - yield ValidationError("%r is too long" % (instance,)) + yield ValidationError(f"{instance!r} is too long") -def dependencies(validator, dependencies, instance, schema): +def dependentRequired(validator, dependentRequired, instance, schema): if not validator.is_type(instance, "object"): return - for property, dependency in iteritems(dependencies): + for property, dependency in dependentRequired.items(): if property not in instance: continue - if validator.is_type(dependency, "array"): - for each in dependency: - if each not in instance: - message = "%r is a dependency of %r" - yield ValidationError(message % (each, property)) - else: - for error in validator.descend( - instance, dependency, schema_path=property, - ): - yield error + for each in dependency: + if each not in instance: + message = f"{each!r} is a dependency of {property!r}" + yield ValidationError(message) + + +def dependentSchemas(validator, dependentSchemas, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, dependency in dependentSchemas.items(): + if property not in instance: + continue + yield from validator.descend( + instance, dependency, schema_path=property, + ) def enum(validator, enums, instance, schema): if instance == 0 or instance == 1: unbooled = unbool(instance) if all(unbooled != unbool(each) for each in enums): - yield ValidationError("%r is not one of %r" % (instance, enums)) + yield ValidationError(f"{instance!r} is not one of {enums!r}") elif instance not in enums: - yield ValidationError("%r is not one of %r" % (instance, enums)) + yield ValidationError(f"{instance!r} is not one of {enums!r}") def ref(validator, ref, instance, schema): resolve = getattr(validator.resolver, "resolve", None) if resolve is None: with validator.resolver.resolving(ref) as resolved: - for error in validator.descend(instance, resolved): - yield error + yield from validator.descend(instance, resolved) else: scope, resolved = validator.resolver.resolve(ref) validator.resolver.push_scope(scope) try: - for error in validator.descend(instance, resolved): - yield error + yield from validator.descend(instance, resolved) finally: validator.resolver.pop_scope() +def dynamicRef(validator, dynamicRef, instance, schema): + _, fragment = urldefrag(dynamicRef) + + for url in validator.resolver._scopes_stack: + lookup_url = urljoin(url, dynamicRef) + with validator.resolver.resolving(lookup_url) as subschema: + if ("$dynamicAnchor" in subschema + and fragment == subschema["$dynamicAnchor"]): + yield from validator.descend(instance, subschema) + break + else: + with validator.resolver.resolving(dynamicRef) as subschema: + yield from validator.descend(instance, subschema) + + def type(validator, types, instance, schema): types = ensure_list(types) if not any(validator.is_type(instance, type) for type in types): - yield ValidationError(types_msg(instance, types)) + reprs = ", ".join(repr(type) for type in types) + yield ValidationError(f"{instance!r} is not of type {reprs}") def properties(validator, properties, instance, schema): if not validator.is_type(instance, "object"): return - for property, subschema in iteritems(properties): + for property, subschema in properties.items(): if property in instance: - for error in validator.descend( + yield from validator.descend( instance[property], subschema, path=property, schema_path=property, - ): - yield error + ) def required(validator, required, instance, schema): @@ -293,27 +342,24 @@ def required(validator, required, instance, schema): return for property in required: if property not in instance: - yield ValidationError("%r is a required property" % property) + yield ValidationError(f"{property!r} is a required property") def minProperties(validator, mP, instance, schema): if validator.is_type(instance, "object") and len(instance) < mP: - yield ValidationError( - "%r does not have enough properties" % (instance,) - ) + yield ValidationError(f"{instance!r} does not have enough properties") def maxProperties(validator, mP, instance, schema): if not validator.is_type(instance, "object"): return if validator.is_type(instance, "object") and len(instance) > mP: - yield ValidationError("%r has too many properties" % (instance,)) + yield ValidationError(f"{instance!r} has too many properties") def allOf(validator, allOf, instance, schema): for index, subschema in enumerate(allOf): - for error in validator.descend(instance, subschema, schema_path=index): - yield error + yield from validator.descend(instance, subschema, schema_path=index) def anyOf(validator, anyOf, instance, schema): @@ -325,7 +371,7 @@ def anyOf(validator, anyOf, instance, schema): all_errors.extend(errs) else: yield ValidationError( - "%r is not valid under any of the given schemas" % (instance,), + f"{instance!r} is not valid under any of the given schemas", context=all_errors, ) @@ -341,33 +387,81 @@ def oneOf(validator, oneOf, instance, schema): all_errors.extend(errs) else: yield ValidationError( - "%r is not valid under any of the given schemas" % (instance,), + f"{instance!r} is not valid under any of the given schemas", context=all_errors, ) - more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)] + more_valid = [ + each for _, each in subschemas + if validator.evolve(schema=each).is_valid(instance) + ] if more_valid: more_valid.append(first_valid) reprs = ", ".join(repr(schema) for schema in more_valid) - yield ValidationError( - "%r is valid under each of %s" % (instance, reprs) - ) + yield ValidationError(f"{instance!r} is valid under each of {reprs}") def not_(validator, not_schema, instance, schema): - if validator.is_valid(instance, not_schema): - yield ValidationError( - "%r is not allowed for %r" % (not_schema, instance) - ) + if validator.evolve(schema=not_schema).is_valid(instance): + message = f"{instance!r} should not be valid under {not_schema!r}" + yield ValidationError(message) def if_(validator, if_schema, instance, schema): - if validator.is_valid(instance, if_schema): - if u"then" in schema: - then = schema[u"then"] - for error in validator.descend(instance, then, schema_path="then"): - yield error - elif u"else" in schema: - else_ = schema[u"else"] - for error in validator.descend(instance, else_, schema_path="else"): - yield error + if validator.evolve(schema=if_schema).is_valid(instance): + if "then" in schema: + then = schema["then"] + yield from validator.descend(instance, then, schema_path="then") + elif "else" in schema: + else_ = schema["else"] + yield from validator.descend(instance, else_, schema_path="else") + + +def unevaluatedItems(validator, unevaluatedItems, instance, schema): + if not validator.is_type(instance, "array"): + return + evaluated_item_indexes = find_evaluated_item_indexes_by_schema( + validator, instance, schema, + ) + unevaluated_items = [ + item for index, item in enumerate(instance) + if index not in evaluated_item_indexes + ] + if unevaluated_items: + error = "Unevaluated items are not allowed (%s %s unexpected)" + yield ValidationError(error % extras_msg(unevaluated_items)) + + +def unevaluatedProperties(validator, unevaluatedProperties, instance, schema): + if not validator.is_type(instance, "object"): + return + evaluated_property_keys = find_evaluated_property_keys_by_schema( + validator, instance, schema, + ) + unevaluated_property_keys = [] + for property in instance: + if property not in evaluated_property_keys: + for _ in validator.descend( + instance[property], + unevaluatedProperties, + path=property, + schema_path=property, + ): + unevaluated_property_keys.append(property) + + if unevaluated_property_keys: + error = "Unevaluated properties are not allowed (%s %s unexpected)" + yield ValidationError(error % extras_msg(unevaluated_property_keys)) + + +def prefixItems(validator, prefixItems, instance, schema): + if not validator.is_type(instance, "array"): + return + + for (index, item), subschema in zip(enumerate(instance), prefixItems): + yield from validator.descend( + instance=item, + schema=subschema, + schema_path=index, + path=index, + ) diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232.py index 65e3aedf7..bf357e911 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232.py @@ -1,19 +1,18 @@ -#!/usr/bin/env python """ A performance benchmark using the example from issue #232. -See https://github.com/Julian/jsonschema/pull/232. +See https://github.com/python-jsonschema/jsonschema/pull/232. """ -from twisted.python.filepath import FilePath +from pathlib import Path + from pyperf import Runner from pyrsistent import m from jsonschema.tests._suite import Version import jsonschema - issue232 = Version( - path=FilePath(__file__).sibling("issue232"), + path=Path(__file__).parent / "issue232", remotes=m(), name="issue232", ) diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232/issue.json b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232/issue.json new file mode 100644 index 000000000..804c34084 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/issue232/issue.json @@ -0,0 +1,2653 @@ +[ + { + "description": "Petstore", + "schema": { + "title": "A JSON Schema for Swagger 2.0 API.", + "id": "http://swagger.io/v2/schema.json#", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "required": [ + "swagger", + "info", + "paths" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "swagger": { + "type": "string", + "enum": [ + "2.0" + ], + "description": "The Swagger version of this document." + }, + "info": { + "$ref": "#/definitions/info" + }, + "host": { + "type": "string", + "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$", + "description": "The host (name or ip) of the API. Example: 'swagger.io'" + }, + "basePath": { + "type": "string", + "pattern": "^/", + "description": "The base path to the API. Example: '/api'." + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "consumes": { + "description": "A list of MIME types accepted by the API.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "paths": { + "$ref": "#/definitions/paths" + }, + "definitions": { + "$ref": "#/definitions/definitions" + }, + "parameters": { + "$ref": "#/definitions/parameterDefinitions" + }, + "responses": { + "$ref": "#/definitions/responseDefinitions" + }, + "security": { + "$ref": "#/definitions/security" + }, + "securityDefinitions": { + "$ref": "#/definitions/securityDefinitions" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/tag" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "definitions": { + "info": { + "type": "object", + "description": "General information about the API.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed." + }, + "termsOfService": { + "type": "string", + "description": "The terms of service for the API." + }, + "contact": { + "$ref": "#/definitions/contact" + }, + "license": { + "$ref": "#/definitions/license" + } + } + }, + "contact": { + "type": "object", + "description": "Contact information for the owners of the API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "license": { + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "paths": { + "type": "object", + "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + }, + "^/": { + "$ref": "#/definitions/pathItem" + } + }, + "additionalProperties": false + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "description": "One or more JSON objects describing the schemas being consumed and produced by the API." + }, + "parameterDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/parameter" + }, + "description": "One or more JSON representations for parameters" + }, + "responseDefinitions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/response" + }, + "description": "One or more JSON representations for parameters" + }, + "externalDocs": { + "type": "object", + "additionalProperties": false, + "description": "information about external documentation", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "examples": { + "type": "object", + "additionalProperties": true + }, + "mimeType": { + "type": "string", + "description": "The MIME type of the HTTP message." + }, + "operation": { + "type": "object", + "required": [ + "responses" + ], + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation, GitHub Flavored Markdown is allowed." + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "operationId": { + "type": "string", + "description": "A unique identifier of the operation." + }, + "produces": { + "description": "A list of MIME types the API can produce.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "consumes": { + "description": "A list of MIME types the API can consume.", + "allOf": [ + { + "$ref": "#/definitions/mediaTypeList" + } + ] + }, + "parameters": { + "$ref": "#/definitions/parametersList" + }, + "responses": { + "$ref": "#/definitions/responses" + }, + "schemes": { + "$ref": "#/definitions/schemesList" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "$ref": "#/definitions/security" + } + } + }, + "pathItem": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "get": { + "$ref": "#/definitions/operation" + }, + "put": { + "$ref": "#/definitions/operation" + }, + "post": { + "$ref": "#/definitions/operation" + }, + "delete": { + "$ref": "#/definitions/operation" + }, + "options": { + "$ref": "#/definitions/operation" + }, + "head": { + "$ref": "#/definitions/operation" + }, + "patch": { + "$ref": "#/definitions/operation" + }, + "parameters": { + "$ref": "#/definitions/parametersList" + } + } + }, + "responses": { + "type": "object", + "description": "Response objects names can either be any valid HTTP status code or 'default'.", + "minProperties": 1, + "additionalProperties": false, + "patternProperties": { + "^([0-9]{3})$|^(default)$": { + "$ref": "#/definitions/responseValue" + }, + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "not": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + } + }, + "responseValue": { + "oneOf": [ + { + "$ref": "#/definitions/response" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "$ref": "#/definitions/fileSchema" + } + ] + }, + "headers": { + "$ref": "#/definitions/headers" + }, + "examples": { + "$ref": "#/definitions/examples" + } + }, + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/header" + } + }, + "header": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "vendorExtension": { + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "bodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "schema" + ], + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "body" + ] + }, + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "schema": { + "$ref": "#/definitions/schema" + } + }, + "additionalProperties": false + }, + "headerParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "header" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "queryParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "query" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "formDataParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "required": { + "type": "boolean", + "description": "Determines whether or not this parameter is required or optional.", + "default": false + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "formData" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "allowEmptyValue": { + "type": "boolean", + "default": false, + "description": "allows sending a parameter by name only or with an empty value." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array", + "file" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormatWithMulti" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "pathParameterSubSchema": { + "additionalProperties": false, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + true + ], + "description": "Determines whether or not this parameter is required or optional." + }, + "in": { + "type": "string", + "description": "Determines the location of the parameter.", + "enum": [ + "path" + ] + }, + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "name": { + "type": "string", + "description": "The name of the parameter." + }, + "type": { + "type": "string", + "enum": [ + "string", + "number", + "boolean", + "integer", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + } + }, + "nonBodyParameter": { + "type": "object", + "required": [ + "name", + "in", + "type" + ], + "oneOf": [ + { + "$ref": "#/definitions/headerParameterSubSchema" + }, + { + "$ref": "#/definitions/formDataParameterSubSchema" + }, + { + "$ref": "#/definitions/queryParameterSubSchema" + }, + { + "$ref": "#/definitions/pathParameterSubSchema" + } + ] + }, + "parameter": { + "oneOf": [ + { + "$ref": "#/definitions/bodyParameter" + }, + { + "$ref": "#/definitions/nonBodyParameter" + } + ] + }, + "schema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "properties": { + "$ref": { + "type": "string" + }, + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "maxProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minProperties": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "type": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/type" + }, + "items": { + "anyOf": [ + { + "$ref": "#/definitions/schema" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/schema" + } + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/schema" + }, + "default": {} + }, + "discriminator": { + "type": "string" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/xml" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "fileSchema": { + "type": "object", + "description": "A deterministic version of a JSON Schema object.", + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + }, + "required": [ + "type" + ], + "properties": { + "format": { + "type": "string" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "required": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" + }, + "type": { + "type": "string", + "enum": [ + "file" + ] + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + }, + "example": {} + }, + "additionalProperties": false + }, + "primitivesItems": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "number", + "integer", + "boolean", + "array" + ] + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/definitions/primitivesItems" + }, + "collectionFormat": { + "$ref": "#/definitions/collectionFormat" + }, + "default": { + "$ref": "#/definitions/default" + }, + "maximum": { + "$ref": "#/definitions/maximum" + }, + "exclusiveMaximum": { + "$ref": "#/definitions/exclusiveMaximum" + }, + "minimum": { + "$ref": "#/definitions/minimum" + }, + "exclusiveMinimum": { + "$ref": "#/definitions/exclusiveMinimum" + }, + "maxLength": { + "$ref": "#/definitions/maxLength" + }, + "minLength": { + "$ref": "#/definitions/minLength" + }, + "pattern": { + "$ref": "#/definitions/pattern" + }, + "maxItems": { + "$ref": "#/definitions/maxItems" + }, + "minItems": { + "$ref": "#/definitions/minItems" + }, + "uniqueItems": { + "$ref": "#/definitions/uniqueItems" + }, + "enum": { + "$ref": "#/definitions/enum" + }, + "multipleOf": { + "$ref": "#/definitions/multipleOf" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/securityRequirement" + }, + "uniqueItems": true + }, + "securityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "xml": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "tag": { + "type": "object", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/externalDocs" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "securityDefinitions": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/basicAuthenticationSecurity" + }, + { + "$ref": "#/definitions/apiKeySecurity" + }, + { + "$ref": "#/definitions/oauth2ImplicitSecurity" + }, + { + "$ref": "#/definitions/oauth2PasswordSecurity" + }, + { + "$ref": "#/definitions/oauth2ApplicationSecurity" + }, + { + "$ref": "#/definitions/oauth2AccessCodeSecurity" + } + ] + } + }, + "basicAuthenticationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "basic" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "apiKeySecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ImplicitSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "implicit" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2PasswordSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "password" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2ApplicationSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "application" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2AccessCodeSecurity": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "flow", + "authorizationUrl", + "tokenUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flow": { + "type": "string", + "enum": [ + "accessCode" + ] + }, + "scopes": { + "$ref": "#/definitions/oauth2Scopes" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + "$ref": "#/definitions/vendorExtension" + } + } + }, + "oauth2Scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mediaTypeList": { + "type": "array", + "items": { + "$ref": "#/definitions/mimeType" + }, + "uniqueItems": true + }, + "parametersList": { + "type": "array", + "description": "The parameters needed to send a valid API call.", + "additionalItems": false, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/parameter" + }, + { + "$ref": "#/definitions/jsonReference" + } + ] + }, + "uniqueItems": true + }, + "schemesList": { + "type": "array", + "description": "The transfer protocol of the API.", + "items": { + "type": "string", + "enum": [ + "http", + "https", + "ws", + "wss" + ] + }, + "uniqueItems": true + }, + "collectionFormat": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes" + ], + "default": "csv" + }, + "collectionFormatWithMulti": { + "type": "string", + "enum": [ + "csv", + "ssv", + "tsv", + "pipes", + "multi" + ], + "default": "csv" + }, + "title": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/title" + }, + "description": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/description" + }, + "default": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/default" + }, + "multipleOf": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf" + }, + "maximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum" + }, + "exclusiveMaximum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum" + }, + "minimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum" + }, + "exclusiveMinimum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum" + }, + "maxLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minLength": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "pattern": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern" + }, + "maxItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger" + }, + "minItems": { + "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0" + }, + "uniqueItems": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems" + }, + "enum": { + "$ref": "http://json-schema.org/draft-04/schema#/properties/enum" + }, + "jsonReference": { + "type": "object", + "required": [ + "$ref" + ], + "additionalProperties": false, + "properties": { + "$ref": { + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "Example petsore", + "data": { + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "http" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/ApiResponse" + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "$ref": "#/definitions/Order" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10.0, + "minimum": 1.0, + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1.0, + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "string" + }, + "headers": { + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + }, + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } + }, + "valid": true + } + ] + } +] diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/json_schema_test_suite.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/json_schema_test_suite.py index 5add5051d..905fb6a3b 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/json_schema_test_suite.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/benchmarks/json_schema_test_suite.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """ A performance benchmark using the official test suite. @@ -9,6 +8,5 @@ from jsonschema.tests._suite import Suite - if __name__ == "__main__": Suite().benchmark(runner=Runner()) diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/cli.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/cli.py index ab3335b27..f93b5c5a0 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/cli.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/cli.py @@ -1,25 +1,154 @@ """ The ``jsonschema`` command line. """ -from __future__ import absolute_import + +from json import JSONDecodeError +from textwrap import dedent import argparse import json import sys +import traceback +import warnings -from jsonschema import __version__ -from jsonschema._reflect import namedAny -from jsonschema.validators import validator_for +try: + from importlib import metadata +except ImportError: + import importlib_metadata as metadata # type: ignore +try: + from pkgutil import resolve_name +except ImportError: + from pkgutil_resolve_name import resolve_name # type: ignore -def _namedAnyWithDefault(name): - if "." not in name: - name = "jsonschema." + name - return namedAny(name) +import attr + +from jsonschema.exceptions import SchemaError +from jsonschema.validators import RefResolver, validator_for + +warnings.warn( + ( + "The jsonschema CLI is deprecated and will be removed in a future " + "version. Please use check-jsonschema instead, which can be installed " + "from https://pypi.org/project/check-jsonschema/" + ), + DeprecationWarning, + stacklevel=2, +) + + +class _CannotLoadFile(Exception): + pass + + +@attr.s +class _Outputter: + + _formatter = attr.ib() + _stdout = attr.ib() + _stderr = attr.ib() + + @classmethod + def from_arguments(cls, arguments, stdout, stderr): + if arguments["output"] == "plain": + formatter = _PlainFormatter(arguments["error_format"]) + elif arguments["output"] == "pretty": + formatter = _PrettyFormatter() + return cls(formatter=formatter, stdout=stdout, stderr=stderr) + + def load(self, path): + try: + file = open(path) + except FileNotFoundError: + self.filenotfound_error(path=path, exc_info=sys.exc_info()) + raise _CannotLoadFile() + + with file: + try: + return json.load(file) + except JSONDecodeError: + self.parsing_error(path=path, exc_info=sys.exc_info()) + raise _CannotLoadFile() + + def filenotfound_error(self, **kwargs): + self._stderr.write(self._formatter.filenotfound_error(**kwargs)) + + def parsing_error(self, **kwargs): + self._stderr.write(self._formatter.parsing_error(**kwargs)) + + def validation_error(self, **kwargs): + self._stderr.write(self._formatter.validation_error(**kwargs)) + + def validation_success(self, **kwargs): + self._stdout.write(self._formatter.validation_success(**kwargs)) + + +@attr.s +class _PrettyFormatter: + + _ERROR_MSG = dedent( + """\ + ===[{type}]===({path})=== + + {body} + ----------------------------- + """, + ) + _SUCCESS_MSG = "===[SUCCESS]===({path})===\n" + + def filenotfound_error(self, path, exc_info): + return self._ERROR_MSG.format( + path=path, + type="FileNotFoundError", + body="{!r} does not exist.".format(path), + ) + + def parsing_error(self, path, exc_info): + exc_type, exc_value, exc_traceback = exc_info + exc_lines = "".join( + traceback.format_exception(exc_type, exc_value, exc_traceback), + ) + return self._ERROR_MSG.format( + path=path, + type=exc_type.__name__, + body=exc_lines, + ) + + def validation_error(self, instance_path, error): + return self._ERROR_MSG.format( + path=instance_path, + type=error.__class__.__name__, + body=error, + ) + + def validation_success(self, instance_path): + return self._SUCCESS_MSG.format(path=instance_path) -def _json_file(path): - with open(path) as file: - return json.load(file) +@attr.s +class _PlainFormatter: + + _error_format = attr.ib() + + def filenotfound_error(self, path, exc_info): + return "{!r} does not exist.\n".format(path) + + def parsing_error(self, path, exc_info): + return "Failed to parse {}: {}\n".format( + "" if path == "" else repr(path), + exc_info[1], + ) + + def validation_error(self, instance_path, error): + return self._error_format.format(file_name=instance_path, error=error) + + def validation_success(self, instance_path): + return "" + + +def _resolve_name_with_default(name): + if "." not in name: + name = "jsonschema." + name + return resolve_name(name) parser = argparse.ArgumentParser( @@ -29,62 +158,142 @@ def _json_file(path): "-i", "--instance", action="append", dest="instances", - type=_json_file, - help=( - "a path to a JSON instance (i.e. filename.json) " - "to validate (may be specified multiple times)" - ), + help=""" + a path to a JSON instance (i.e. filename.json) to validate (may + be specified multiple times). If no instances are provided via this + option, one will be expected on standard input. + """, ) parser.add_argument( "-F", "--error-format", - default="{error.instance}: {error.message}\n", - help=( - "the format to use for each error output message, specified in " - "a form suitable for passing to str.format, which will be called " - "with 'error' for each error" - ), + help=""" + the format to use for each validation error message, specified + in a form suitable for str.format. This string will be passed + one formatted object named 'error' for each ValidationError. + Only provide this option when using --output=plain, which is the + default. If this argument is unprovided and --output=plain is + used, a simple default representation will be used. + """, +) +parser.add_argument( + "-o", "--output", + choices=["plain", "pretty"], + default="plain", + help=""" + an output format to use. 'plain' (default) will produce minimal + text with one line for each error, while 'pretty' will produce + more detailed human-readable output on multiple lines. + """, ) parser.add_argument( "-V", "--validator", - type=_namedAnyWithDefault, - help=( - "the fully qualified object name of a validator to use, or, for " - "validators that are registered with jsonschema, simply the name " - "of the class." - ), + type=_resolve_name_with_default, + help=""" + the fully qualified object name of a validator to use, or, for + validators that are registered with jsonschema, simply the name + of the class. + """, +) +parser.add_argument( + "--base-uri", + help=""" + a base URI to assign to the provided schema, even if it does not + declare one (via e.g. $id). This option can be used if you wish to + resolve relative references to a particular URI (or local path) + """, ) parser.add_argument( "--version", action="version", - version=__version__, + version=metadata.version("jsonschema"), ) parser.add_argument( "schema", - help="the JSON Schema to validate with (i.e. schema.json)", - type=_json_file, + help="the path to a JSON Schema to validate with (i.e. schema.json)", ) def parse_args(args): arguments = vars(parser.parse_args(args=args or ["--help"])) - if arguments["validator"] is None: - arguments["validator"] = validator_for(arguments["schema"]) + if arguments["output"] != "plain" and arguments["error_format"]: + raise parser.error( + "--error-format can only be used with --output plain", + ) + if arguments["output"] == "plain" and arguments["error_format"] is None: + arguments["error_format"] = "{error.instance}: {error.message}\n" return arguments +def _validate_instance(instance_path, instance, validator, outputter): + invalid = False + for error in validator.iter_errors(instance): + invalid = True + outputter.validation_error(instance_path=instance_path, error=error) + + if not invalid: + outputter.validation_success(instance_path=instance_path) + return invalid + + def main(args=sys.argv[1:]): sys.exit(run(arguments=parse_args(args=args))) -def run(arguments, stdout=sys.stdout, stderr=sys.stderr): - error_format = arguments["error_format"] - validator = arguments["validator"](schema=arguments["schema"]) +def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin): + outputter = _Outputter.from_arguments( + arguments=arguments, + stdout=stdout, + stderr=stderr, + ) + + try: + schema = outputter.load(arguments["schema"]) + except _CannotLoadFile: + return 1 + + if arguments["validator"] is None: + arguments["validator"] = validator_for(schema) + + try: + arguments["validator"].check_schema(schema) + except SchemaError as error: + outputter.validation_error( + instance_path=arguments["schema"], + error=error, + ) + return 1 + + if arguments["instances"]: + load, instances = outputter.load, arguments["instances"] + else: + def load(_): + try: + return json.load(stdin) + except JSONDecodeError: + outputter.parsing_error( + path="", exc_info=sys.exc_info(), + ) + raise _CannotLoadFile() + instances = [""] + + resolver = RefResolver( + base_uri=arguments["base_uri"], + referrer=schema, + ) if arguments["base_uri"] is not None else None - validator.check_schema(arguments["schema"]) + validator = arguments["validator"](schema, resolver=resolver) + exit_code = 0 + for each in instances: + try: + instance = load(each) + except _CannotLoadFile: + exit_code = 1 + else: + exit_code |= _validate_instance( + instance_path=each, + instance=instance, + validator=validator, + outputter=outputter, + ) - errored = False - for instance in arguments["instances"] or (): - for error in validator.iter_errors(instance): - stderr.write(error_format.format(error=error)) - errored = True - return errored + return exit_code diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/compat.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/compat.py deleted file mode 100644 index 47e098045..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/compat.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Python 2/3 compatibility helpers. - -Note: This module is *not* public API. -""" -import contextlib -import operator -import sys - - -try: - from collections.abc import MutableMapping, Sequence # noqa -except ImportError: - from collections import MutableMapping, Sequence # noqa - -PY3 = sys.version_info[0] >= 3 - -if PY3: - zip = zip - from functools import lru_cache - from io import StringIO as NativeIO - from urllib.parse import ( - unquote, urljoin, urlunsplit, SplitResult, urlsplit - ) - from urllib.request import pathname2url, urlopen - str_types = str, - int_types = int, - iteritems = operator.methodcaller("items") -else: - from itertools import izip as zip # noqa - from io import BytesIO as NativeIO - from urlparse import urljoin, urlunsplit, SplitResult, urlsplit - from urllib import pathname2url, unquote # noqa - import urllib2 # noqa - def urlopen(*args, **kwargs): - return contextlib.closing(urllib2.urlopen(*args, **kwargs)) - - str_types = basestring - int_types = int, long - iteritems = operator.methodcaller("iteritems") - - from functools32 import lru_cache - - -def urldefrag(url): - if "#" in url: - s, n, p, q, frag = urlsplit(url) - defrag = urlunsplit((s, n, p, q, "")) - else: - defrag = url - frag = "" - return defrag, frag - - -# flake8: noqa diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/exceptions.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/exceptions.py index 691dcffe6..87db3df3a 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/exceptions.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/exceptions.py @@ -1,19 +1,20 @@ """ Validation errors, and some surrounding helpers. """ +from __future__ import annotations + from collections import defaultdict, deque +from pprint import pformat +from textwrap import dedent, indent +import heapq import itertools -import pprint -import textwrap import attr from jsonschema import _utils -from jsonschema.compat import PY3, iteritems - -WEAK_MATCHES = frozenset(["anyOf", "oneOf"]) -STRONG_MATCHES = frozenset() +WEAK_MATCHES: frozenset[str] = frozenset(["anyOf", "oneOf"]) +STRONG_MATCHES: frozenset[str] = frozenset() _unset = _utils.Unset() @@ -31,6 +32,7 @@ def __init__( schema=_unset, schema_path=(), parent=None, + type_checker=_unset, ): super(_Error, self).__init__( message, @@ -54,45 +56,42 @@ def __init__( self.instance = instance self.schema = schema self.parent = parent + self._type_checker = type_checker for error in context: error.parent = self def __repr__(self): - return "<%s: %r>" % (self.__class__.__name__, self.message) + return f"<{self.__class__.__name__}: {self.message!r}>" - def __unicode__(self): + def __str__(self): essential_for_verbose = ( self.validator, self.validator_value, self.instance, self.schema, ) if any(m is _unset for m in essential_for_verbose): return self.message - pschema = pprint.pformat(self.schema, width=72) - pinstance = pprint.pformat(self.instance, width=72) - return self.message + textwrap.dedent(""" - - Failed validating %r in %s%s: - %s - - On %s%s: - %s - """.rstrip() - ) % ( - self.validator, - self._word_for_schema_in_error_message, - _utils.format_as_index(list(self.relative_schema_path)[:-1]), - _utils.indent(pschema), - self._word_for_instance_in_error_message, - _utils.format_as_index(self.relative_path), - _utils.indent(pinstance), + schema_path = _utils.format_as_index( + container=self._word_for_schema_in_error_message, + indices=list(self.relative_schema_path)[:-1], ) + instance_path = _utils.format_as_index( + container=self._word_for_instance_in_error_message, + indices=self.relative_path, + ) + prefix = 16 * " " + + return dedent( + f"""\ + {self.message} + + Failed validating {self.validator!r} in {schema_path}: + {indent(pformat(self.schema, width=72), prefix).lstrip()} - if PY3: - __str__ = __unicode__ - else: - def __str__(self): - return unicode(self).encode("utf-8") + On {instance_path}: + {indent(pformat(self.instance, width=72), prefix).lstrip()} + """.rstrip(), + ) @classmethod def create_from(cls, other): @@ -118,8 +117,21 @@ def absolute_schema_path(self): path.extendleft(reversed(parent.absolute_schema_path)) return path - def _set(self, **kwargs): - for k, v in iteritems(kwargs): + @property + def json_path(self): + path = "$" + for elem in self.absolute_path: + if isinstance(elem, int): + path += "[" + str(elem) + "]" + else: + path += "." + elem + return path + + def _set(self, type_checker=None, **kwargs): + if type_checker is not None and self._type_checker is _unset: + self._type_checker = type_checker + + for k, v in kwargs.items(): if getattr(self, k) is _unset: setattr(self, k, v) @@ -130,6 +142,20 @@ def _contents(self): ) return dict((attr, getattr(self, attr)) for attr in attrs) + def _matches_type(self): + try: + expected = self.schema["type"] + except (KeyError, TypeError): + return False + + if isinstance(expected, str): + return self._type_checker.is_type(self.instance, expected) + + return any( + self._type_checker.is_type(self.instance, expected_type) + for expected_type in expected + ) + class ValidationError(_Error): """ @@ -169,14 +195,8 @@ class UndefinedTypeCheck(Exception): def __init__(self, type): self.type = type - def __unicode__(self): - return "Type %r is unknown to this type checker" % self.type - - if PY3: - __str__ = __unicode__ - else: - def __str__(self): - return unicode(self).encode("utf-8") + def __str__(self): + return f"Type {self.type!r} is unknown to this type checker" class UnknownType(Exception): @@ -189,23 +209,18 @@ def __init__(self, type, instance, schema): self.instance = instance self.schema = schema - def __unicode__(self): - pschema = pprint.pformat(self.schema, width=72) - pinstance = pprint.pformat(self.instance, width=72) - return textwrap.dedent(""" - Unknown type %r for validator with schema: - %s + def __str__(self): + prefix = 16 * " " + + return dedent( + f"""\ + Unknown type {self.type!r} for validator with schema: + {indent(pformat(self.schema, width=72), prefix).lstrip()} While checking instance: - %s - """.rstrip() - ) % (self.type, _utils.indent(pschema), _utils.indent(pinstance)) - - if PY3: - __str__ = __unicode__ - else: - def __str__(self): - return unicode(self).encode("utf-8") + {indent(pformat(self.instance, width=72), prefix).lstrip()} + """.rstrip(), + ) class FormatError(Exception): @@ -218,17 +233,11 @@ def __init__(self, message, cause=None): self.message = message self.cause = self.__cause__ = cause - def __unicode__(self): + def __str__(self): return self.message - if PY3: - __str__ = __unicode__ - else: - def __str__(self): - return self.message.encode("utf-8") - -class ErrorTree(object): +class ErrorTree: """ ErrorTrees make it easier to check which validations failed. """ @@ -258,10 +267,10 @@ def __getitem__(self, index): """ Retrieve the child tree one level down at the given ``index``. - If the index is not in the instance that this tree corresponds to and - is not known by this tree, whatever error would be raised by - ``instance.__getitem__`` will be propagated (usually this is some - subclass of `exceptions.LookupError`. + If the index is not in the instance that this tree corresponds + to and is not known by this tree, whatever error would be raised + by ``instance.__getitem__`` will be propagated (usually this is + some subclass of `LookupError`. """ if self._instance is not _unset and index not in self: @@ -288,7 +297,9 @@ def __len__(self): return self.total_errors def __repr__(self): - return "<%s (%s total errors)>" % (self.__class__.__name__, len(self)) + total = len(self) + errors = "error" if total == 1 else "errors" + return f"<{self.__class__.__name__} ({total} total {errors})>" @property def total_errors(self): @@ -296,7 +307,7 @@ def total_errors(self): The total number of errors in the entire tree, including children. """ - child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) + child_errors = sum(len(tree) for _, tree in self._contents.items()) return len(self.errors) + child_errors @@ -306,19 +317,25 @@ def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES): Arguments: weak (set): - a collection of validator names to consider to be "weak". - If there are two errors at the same level of the instance - and one is in the set of weak validator names, the other - error will take priority. By default, :validator:`anyOf` and - :validator:`oneOf` are considered weak validators and will - be superseded by other same-level validation errors. + a collection of validation keywords to consider to be + "weak". If there are two errors at the same level of the + instance and one is in the set of weak validation keywords, + the other error will take priority. By default, :kw:`anyOf` + and :kw:`oneOf` are considered weak keywords and will be + superseded by other same-level validation errors. strong (set): - a collection of validator names to consider to be "strong" + a collection of validation keywords to consider to be + "strong" """ def relevance(error): validator = error.validator - return -len(error.path), validator not in weak, validator in strong + return ( + -len(error.path), + validator not in weak, + validator in strong, + not error._matches_type(), + ) return relevance @@ -333,20 +350,20 @@ def best_match(errors, key=relevance): `ValidationError.path` is shorter) are considered better matches, since they indicate "more" is wrong with the instance. - If the resulting match is either :validator:`oneOf` or :validator:`anyOf`, - the *opposite* assumption is made -- i.e. the deepest error is picked, - since these validators only need to match once, and any other errors may - not be relevant. + If the resulting match is either :kw:`oneOf` or :kw:`anyOf`, the + *opposite* assumption is made -- i.e. the deepest error is picked, + since these keywords only need to match once, and any other errors + may not be relevant. Arguments: - errors (collections.Iterable): + errors (collections.abc.Iterable): the errors to select from. Do not provide a mixture of errors from different validation attempts (i.e. from different instances or schemas), since it won't produce sensical output. - key (collections.Callable): + key (collections.abc.Callable): the key to use when sorting errors. See `relevance` and transitively `by_relevance` for more details (the default is @@ -370,5 +387,10 @@ def best_match(errors, key=relevance): best = max(itertools.chain([best], errors), key=key) while best.context: - best = min(best.context, key=key) + # Calculate the minimum via nsmallest, because we don't recurse if + # all nested errors have the same relevance (i.e. if min == max == all) + smallest = heapq.nsmallest(2, best.context, key=key) + if len(smallest) == 2 and key(smallest[0]) == key(smallest[1]): + return best + best = smallest[0] return best diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/protocols.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/protocols.py new file mode 100644 index 000000000..2a8f00dda --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/protocols.py @@ -0,0 +1,224 @@ +""" +typing.Protocol classes for jsonschema interfaces. +""" + +# for reference material on Protocols, see +# https://www.python.org/dev/peps/pep-0544/ + +from __future__ import annotations + +from collections.abc import Callable, Mapping +from typing import TYPE_CHECKING, Any, ClassVar, Iterable +import sys + +# doing these imports with `try ... except ImportError` doesn't pass mypy +# checking because mypy sees `typing._SpecialForm` and +# `typing_extensions._SpecialForm` as incompatible +# +# see: +# https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module +# https://github.com/python/mypy/issues/4427 +if sys.version_info >= (3, 8): + from typing import Protocol, runtime_checkable +else: + from typing_extensions import Protocol, runtime_checkable + +# in order for Sphinx to resolve references accurately from type annotations, +# it needs to see names like `jsonschema.TypeChecker` +# therefore, only import at type-checking time (to avoid circular references), +# but use `jsonschema` for any types which will otherwise not be resolvable +if TYPE_CHECKING: + import jsonschema + +from jsonschema.exceptions import ValidationError + +# For code authors working on the validator protocol, these are the three +# use-cases which should be kept in mind: +# +# 1. As a protocol class, it can be used in type annotations to describe the +# available methods and attributes of a validator +# 2. It is the source of autodoc for the validator documentation +# 3. It is runtime_checkable, meaning that it can be used in isinstance() +# checks. +# +# Since protocols are not base classes, isinstance() checking is limited in +# its capabilities. See docs on runtime_checkable for detail + + +@runtime_checkable +class Validator(Protocol): + """ + The protocol to which all validator classes adhere. + + Arguments: + + schema: + + The schema that the validator object will validate with. + It is assumed to be valid, and providing + an invalid schema can lead to undefined behavior. See + `Validator.check_schema` to validate a schema first. + + resolver: + + a resolver that will be used to resolve :kw:`$ref` + properties (JSON references). If unprovided, one will be created. + + format_checker: + + if provided, a checker which will be used to assert about + :kw:`format` properties present in the schema. If unprovided, + *no* format validation is done, and the presence of format + within schemas is strictly informational. Certain formats + require additional packages to be installed in order to assert + against instances. Ensure you've installed `jsonschema` with + its `extra (optional) dependencies ` when + invoking ``pip``. + + .. deprecated:: v4.12.0 + + Subclassing validator classes now explicitly warns this is not part of + their public API. + """ + + #: An object representing the validator's meta schema (the schema that + #: describes valid schemas in the given version). + META_SCHEMA: ClassVar[Mapping] + + #: A mapping of validation keywords (`str`\s) to functions that + #: validate the keyword with that name. For more information see + #: `creating-validators`. + VALIDATORS: ClassVar[Mapping] + + #: A `jsonschema.TypeChecker` that will be used when validating + #: :kw:`type` keywords in JSON schemas. + TYPE_CHECKER: ClassVar[jsonschema.TypeChecker] + + #: A `jsonschema.FormatChecker` that will be used when validating + #: :kw:`format` keywords in JSON schemas. + FORMAT_CHECKER: ClassVar[jsonschema.FormatChecker] + + #: A function which given a schema returns its ID. + ID_OF: Callable[[Any], str | None] + + #: The schema that will be used to validate instances + schema: Mapping | bool + + def __init__( + self, + schema: Mapping | bool, + resolver: jsonschema.RefResolver | None = None, + format_checker: jsonschema.FormatChecker | None = None, + ) -> None: + ... + + @classmethod + def check_schema(cls, schema: Mapping | bool) -> None: + """ + Validate the given schema against the validator's `META_SCHEMA`. + + Raises: + + `jsonschema.exceptions.SchemaError`: + + if the schema is invalid + """ + + def is_type(self, instance: Any, type: str) -> bool: + """ + Check if the instance is of the given (JSON Schema) type. + + Arguments: + + instance: + + the value to check + + type: + + the name of a known (JSON Schema) type + + Returns: + + whether the instance is of the given type + + Raises: + + `jsonschema.exceptions.UnknownType`: + + if ``type`` is not a known type + """ + + def is_valid(self, instance: Any) -> bool: + """ + Check if the instance is valid under the current `schema`. + + Returns: + + whether the instance is valid or not + + >>> schema = {"maxItems" : 2} + >>> Draft202012Validator(schema).is_valid([2, 3, 4]) + False + """ + + def iter_errors(self, instance: Any) -> Iterable[ValidationError]: + r""" + Lazily yield each of the validation errors in the given instance. + + >>> schema = { + ... "type" : "array", + ... "items" : {"enum" : [1, 2, 3]}, + ... "maxItems" : 2, + ... } + >>> v = Draft202012Validator(schema) + >>> for error in sorted(v.iter_errors([2, 3, 4]), key=str): + ... print(error.message) + 4 is not one of [1, 2, 3] + [2, 3, 4] is too long + + .. deprecated:: v4.0.0 + + Calling this function with a second schema argument is deprecated. + Use `Validator.evolve` instead. + """ + + def validate(self, instance: Any) -> None: + """ + Check if the instance is valid under the current `schema`. + + Raises: + + `jsonschema.exceptions.ValidationError`: + + if the instance is invalid + + >>> schema = {"maxItems" : 2} + >>> Draft202012Validator(schema).validate([2, 3, 4]) + Traceback (most recent call last): + ... + ValidationError: [2, 3, 4] is too long + """ + + def evolve(self, **kwargs) -> "Validator": + """ + Create a new validator like this one, but with given changes. + + Preserves all other attributes, so can be used to e.g. create a + validator with a different schema but with the same :kw:`$ref` + resolution behavior. + + >>> validator = Draft202012Validator({}) + >>> validator.evolve(schema={"type": "number"}) + Draft202012Validator(schema={'type': 'number'}, format_checker=None) + + The returned object satisfies the validator protocol, but may not + be of the same concrete class! In particular this occurs + when a :kw:`$ref` occurs to a schema with a different + :kw:`$schema` than this one (i.e. for a different draft). + + >>> validator.evolve( + ... schema={"$schema": Draft7Validator.META_SCHEMA["$id"]} + ... ) + Draft7Validator(schema=..., format_checker=None) + """ diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft2019-09.json b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft2019-09.json new file mode 100644 index 000000000..2248a0c80 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft2019-09.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true, + "https://json-schema.org/draft/2019-09/vocab/applicator": true, + "https://json-schema.org/draft/2019-09/vocab/validation": true, + "https://json-schema.org/draft/2019-09/vocab/meta-data": true, + "https://json-schema.org/draft/2019-09/vocab/format": false, + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "properties": { + "definitions": { + "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + } + } + } +} diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft2020-12.json b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft2020-12.json new file mode 100644 index 000000000..d5e2d31c3 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft2020-12.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/unevaluated"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format-annotation"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", + "properties": { + "definitions": { + "$comment": "\"definitions\" has been replaced by \"$defs\".", + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "deprecated": true, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$dynamicRef": "#meta" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + }, + "deprecated": true, + "default": {} + }, + "$recursiveAnchor": { + "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", + "$ref": "meta/core#/$defs/anchorString", + "deprecated": true + }, + "$recursiveRef": { + "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", + "$ref": "meta/core#/$defs/uriReferenceString", + "deprecated": true + } + } +} diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft3.json b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft3.json index f8a09c563..8b26b1f89 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft3.json +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft3.json @@ -1,199 +1,172 @@ { - "$schema": "http://json-schema.org/draft-03/schema#", - "dependencies": { - "exclusiveMaximum": "maximum", - "exclusiveMinimum": "minimum" - }, - "id": "http://json-schema.org/draft-03/schema#", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "additionalItems": { - "default": {}, - "type": [ - { - "$ref": "#" - }, - "boolean" - ] - }, - "additionalProperties": { - "default": {}, - "type": [ - { - "$ref": "#" - }, - "boolean" - ] - }, - "default": { - "type": "any" - }, - "dependencies": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": [ - "string", - "array", - { - "$ref": "#" - } - ] - }, - "default": {}, - "type": [ - "string", - "array", - "object" - ] - }, - "description": { - "type": "string" - }, - "disallow": { - "items": { - "type": [ - "string", - { - "$ref": "#" - } - ] - }, - "type": [ - "string", - "array" - ], - "uniqueItems": true - }, - "divisibleBy": { - "default": 1, - "exclusiveMinimum": true, - "minimum": 0, - "type": "number" - }, - "enum": { - "type": "array" - }, - "exclusiveMaximum": { - "default": false, - "type": "boolean" - }, - "exclusiveMinimum": { - "default": false, - "type": "boolean" - }, - "extends": { - "default": {}, - "items": { - "$ref": "#" - }, - "type": [ - { - "$ref": "#" - }, - "array" - ] - }, - "format": { - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "items": { - "default": {}, - "items": { - "$ref": "#" - }, - "type": [ - { - "$ref": "#" - }, - "array" - ] - }, - "maxDecimal": { - "minimum": 0, - "type": "number" - }, - "maxItems": { - "minimum": 0, - "type": "integer" - }, - "maxLength": { - "type": "integer" - }, - "maximum": { - "type": "number" - }, - "minItems": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "minLength": { - "default": 0, - "minimum": 0, - "type": "integer" - }, - "minimum": { - "type": "number" - }, - "pattern": { - "format": "regex", - "type": "string" - }, - "patternProperties": { - "additionalProperties": { - "$ref": "#" - }, - "default": {}, - "type": "object" - }, - "properties": { - "additionalProperties": { - "$ref": "#", - "type": "object" - }, - "default": {}, - "type": "object" - }, - "required": { - "default": false, - "type": "boolean" - }, - "title": { - "type": "string" - }, - "type": { - "default": "any", - "items": { - "type": [ - "string", - { - "$ref": "#" - } - ] - }, - "type": [ - "string", - "array" - ], - "uniqueItems": true - }, - "uniqueItems": { - "default": false, - "type": "boolean" - } - }, - "type": "object" + "$schema" : "http://json-schema.org/draft-03/schema#", + "id" : "http://json-schema.org/draft-03/schema#", + "type" : "object", + + "properties" : { + "type" : { + "type" : ["string", "array"], + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + "uniqueItems" : true, + "default" : "any" + }, + + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {} + }, + + "patternProperties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {} + }, + + "additionalProperties" : { + "type" : [{"$ref" : "#"}, "boolean"], + "default" : {} + }, + + "items" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "default" : {} + }, + + "additionalItems" : { + "type" : [{"$ref" : "#"}, "boolean"], + "default" : {} + }, + + "required" : { + "type" : "boolean", + "default" : false + }, + + "dependencies" : { + "type" : "object", + "additionalProperties" : { + "type" : ["string", "array", {"$ref" : "#"}], + "items" : { + "type" : "string" + } + }, + "default" : {} + }, + + "minimum" : { + "type" : "number" + }, + + "maximum" : { + "type" : "number" + }, + + "exclusiveMinimum" : { + "type" : "boolean", + "default" : false + }, + + "exclusiveMaximum" : { + "type" : "boolean", + "default" : false + }, + + "minItems" : { + "type" : "integer", + "minimum" : 0, + "default" : 0 + }, + + "maxItems" : { + "type" : "integer", + "minimum" : 0 + }, + + "uniqueItems" : { + "type" : "boolean", + "default" : false + }, + + "pattern" : { + "type" : "string", + "format" : "regex" + }, + + "minLength" : { + "type" : "integer", + "minimum" : 0, + "default" : 0 + }, + + "maxLength" : { + "type" : "integer" + }, + + "enum" : { + "type" : "array", + "minItems" : 1, + "uniqueItems" : true + }, + + "default" : { + "type" : "any" + }, + + "title" : { + "type" : "string" + }, + + "description" : { + "type" : "string" + }, + + "format" : { + "type" : "string" + }, + + "divisibleBy" : { + "type" : "number", + "minimum" : 0, + "exclusiveMinimum" : true, + "default" : 1 + }, + + "disallow" : { + "type" : ["string", "array"], + "items" : { + "type" : ["string", {"$ref" : "#"}] + }, + "uniqueItems" : true + }, + + "extends" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "default" : {} + }, + + "id" : { + "type" : "string" + }, + + "$ref" : { + "type" : "string" + }, + + "$schema" : { + "type" : "string", + "format" : "uri" + } + }, + + "dependencies" : { + "exclusiveMinimum" : "minimum", + "exclusiveMaximum" : "maximum" + }, + + "default" : {} } diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft4.json b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft4.json index 9b666cff8..bcbb84743 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft4.json +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/draft4.json @@ -1,222 +1,149 @@ { + "id": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#", - "default": {}, + "description": "Core schema meta-schema", "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, "positiveInteger": { - "minimum": 0, - "type": "integer" + "type": "integer", + "minimum": 0 }, "positiveIntegerDefault0": { - "allOf": [ - { - "$ref": "#/definitions/positiveInteger" - }, - { - "default": 0 - } - ] - }, - "schemaArray": { - "items": { - "$ref": "#" - }, - "minItems": 1, - "type": "array" + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] }, "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] }, "stringArray": { - "items": { - "type": "string" - }, - "minItems": 1, "type": "array", + "items": { "type": "string" }, + "minItems": 1, "uniqueItems": true } }, - "dependencies": { - "exclusiveMaximum": [ - "maximum" - ], - "exclusiveMinimum": [ - "minimum" - ] - }, - "description": "Core schema meta-schema", - "id": "http://json-schema.org/draft-04/schema#", + "type": "object", "properties": { - "$schema": { - "format": "uri", + "id": { "type": "string" }, - "additionalItems": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#" - } - ], - "default": {} - }, - "additionalProperties": { - "anyOf": [ - { - "type": "boolean" - }, - { - "$ref": "#" - } - ], - "default": {} - }, - "allOf": { - "$ref": "#/definitions/schemaArray" - }, - "anyOf": { - "$ref": "#/definitions/schemaArray" - }, - "default": {}, - "definitions": { - "additionalProperties": { - "$ref": "#" - }, - "default": {}, - "type": "object" + "$schema": { + "type": "string" }, - "dependencies": { - "additionalProperties": { - "anyOf": [ - { - "$ref": "#" - }, - { - "$ref": "#/definitions/stringArray" - } - ] - }, - "type": "object" + "title": { + "type": "string" }, "description": { "type": "string" }, - "enum": { - "type": "array" + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" }, "exclusiveMaximum": { - "default": false, - "type": "boolean" + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" }, "exclusiveMinimum": { - "default": false, - "type": "boolean" + "type": "boolean", + "default": false }, - "format": { - "type": "string" + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" }, - "id": { - "format": "uri", - "type": "string" + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} }, "items": { "anyOf": [ - { - "$ref": "#" - }, - { - "$ref": "#/definitions/schemaArray" - } + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } ], "default": {} }, - "maxItems": { - "$ref": "#/definitions/positiveInteger" - }, - "maxLength": { - "$ref": "#/definitions/positiveInteger" - }, - "maxProperties": { - "$ref": "#/definitions/positiveInteger" - }, - "maximum": { - "type": "number" - }, - "minItems": { - "$ref": "#/definitions/positiveIntegerDefault0" - }, - "minLength": { - "$ref": "#/definitions/positiveIntegerDefault0" - }, - "minProperties": { - "$ref": "#/definitions/positiveIntegerDefault0" - }, - "minimum": { - "type": "number" - }, - "multipleOf": { - "exclusiveMinimum": true, - "minimum": 0, - "type": "number" + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false }, - "not": { - "$ref": "#" + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} }, - "oneOf": { - "$ref": "#/definitions/schemaArray" + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} }, - "pattern": { - "format": "regex", - "type": "string" + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} }, "patternProperties": { - "additionalProperties": { - "$ref": "#" - }, - "default": {}, - "type": "object" + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} }, - "properties": { + "dependencies": { + "type": "object", "additionalProperties": { - "$ref": "#" - }, - "default": {}, - "type": "object" - }, - "required": { - "$ref": "#/definitions/stringArray" + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } }, - "title": { - "type": "string" + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true }, "type": { "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, { - "$ref": "#/definitions/simpleTypes" - }, - { - "items": { - "$ref": "#/definitions/simpleTypes" - }, - "minItems": 1, "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, "uniqueItems": true } ] }, - "uniqueItems": { - "default": false, - "type": "boolean" - } + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] }, - "type": "object" + "default": {} } diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/vocabularies.json b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/vocabularies.json new file mode 100644 index 000000000..bca170527 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/schemas/vocabularies.json @@ -0,0 +1 @@ +{"https://json-schema.org/draft/2020-12/meta/content": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/content", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/content": true}, "$dynamicAnchor": "meta", "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"contentEncoding": {"type": "string"}, "contentMediaType": {"type": "string"}, "contentSchema": {"$dynamicRef": "#meta"}}}, "https://json-schema.org/draft/2020-12/meta/unevaluated": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/unevaluated": true}, "$dynamicAnchor": "meta", "title": "Unevaluated applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"unevaluatedItems": {"$dynamicRef": "#meta"}, "unevaluatedProperties": {"$dynamicRef": "#meta"}}}, "https://json-schema.org/draft/2020-12/meta/format-annotation": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/format-annotation": true}, "$dynamicAnchor": "meta", "title": "Format vocabulary meta-schema for annotation results", "type": ["object", "boolean"], "properties": {"format": {"type": "string"}}}, "https://json-schema.org/draft/2020-12/meta/applicator": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/applicator", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/applicator": true}, "$dynamicAnchor": "meta", "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"prefixItems": {"$ref": "#/$defs/schemaArray"}, "items": {"$dynamicRef": "#meta"}, "contains": {"$dynamicRef": "#meta"}, "additionalProperties": {"$dynamicRef": "#meta"}, "properties": {"type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "default": {}}, "patternProperties": {"type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "propertyNames": {"format": "regex"}, "default": {}}, "dependentSchemas": {"type": "object", "additionalProperties": {"$dynamicRef": "#meta"}, "default": {}}, "propertyNames": {"$dynamicRef": "#meta"}, "if": {"$dynamicRef": "#meta"}, "then": {"$dynamicRef": "#meta"}, "else": {"$dynamicRef": "#meta"}, "allOf": {"$ref": "#/$defs/schemaArray"}, "anyOf": {"$ref": "#/$defs/schemaArray"}, "oneOf": {"$ref": "#/$defs/schemaArray"}, "not": {"$dynamicRef": "#meta"}}, "$defs": {"schemaArray": {"type": "array", "minItems": 1, "items": {"$dynamicRef": "#meta"}}}}, "https://json-schema.org/draft/2020-12/meta/meta-data": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/meta-data": true}, "$dynamicAnchor": "meta", "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"title": {"type": "string"}, "description": {"type": "string"}, "default": true, "deprecated": {"type": "boolean", "default": false}, "readOnly": {"type": "boolean", "default": false}, "writeOnly": {"type": "boolean", "default": false}, "examples": {"type": "array", "items": true}}}, "https://json-schema.org/draft/2020-12/meta/core": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/core", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/core": true}, "$dynamicAnchor": "meta", "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"$id": {"$ref": "#/$defs/uriReferenceString", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$"}, "$schema": {"$ref": "#/$defs/uriString"}, "$ref": {"$ref": "#/$defs/uriReferenceString"}, "$anchor": {"$ref": "#/$defs/anchorString"}, "$dynamicRef": {"$ref": "#/$defs/uriReferenceString"}, "$dynamicAnchor": {"$ref": "#/$defs/anchorString"}, "$vocabulary": {"type": "object", "propertyNames": {"$ref": "#/$defs/uriString"}, "additionalProperties": {"type": "boolean"}}, "$comment": {"type": "string"}, "$defs": {"type": "object", "additionalProperties": {"$dynamicRef": "#meta"}}}, "$defs": {"anchorString": {"type": "string", "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$"}, "uriString": {"type": "string", "format": "uri"}, "uriReferenceString": {"type": "string", "format": "uri-reference"}}}, "https://json-schema.org/draft/2020-12/meta/validation": {"$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/meta/validation", "$vocabulary": {"https://json-schema.org/draft/2020-12/vocab/validation": true}, "$dynamicAnchor": "meta", "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"type": {"anyOf": [{"$ref": "#/$defs/simpleTypes"}, {"type": "array", "items": {"$ref": "#/$defs/simpleTypes"}, "minItems": 1, "uniqueItems": true}]}, "const": true, "enum": {"type": "array", "items": true}, "multipleOf": {"type": "number", "exclusiveMinimum": 0}, "maximum": {"type": "number"}, "exclusiveMaximum": {"type": "number"}, "minimum": {"type": "number"}, "exclusiveMinimum": {"type": "number"}, "maxLength": {"$ref": "#/$defs/nonNegativeInteger"}, "minLength": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "pattern": {"type": "string", "format": "regex"}, "maxItems": {"$ref": "#/$defs/nonNegativeInteger"}, "minItems": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "uniqueItems": {"type": "boolean", "default": false}, "maxContains": {"$ref": "#/$defs/nonNegativeInteger"}, "minContains": {"$ref": "#/$defs/nonNegativeInteger", "default": 1}, "maxProperties": {"$ref": "#/$defs/nonNegativeInteger"}, "minProperties": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/$defs/stringArray"}, "dependentRequired": {"type": "object", "additionalProperties": {"$ref": "#/$defs/stringArray"}}}, "$defs": {"nonNegativeInteger": {"type": "integer", "minimum": 0}, "nonNegativeIntegerDefault0": {"$ref": "#/$defs/nonNegativeInteger", "default": 0}, "simpleTypes": {"enum": ["array", "boolean", "integer", "null", "number", "object", "string"]}, "stringArray": {"type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": []}}}, "https://json-schema.org/draft/2019-09/meta/content": {"$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/content", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/content": true}, "$recursiveAnchor": true, "title": "Content vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"contentMediaType": {"type": "string"}, "contentEncoding": {"type": "string"}, "contentSchema": {"$recursiveRef": "#"}}}, "https://json-schema.org/draft/2019-09/meta/applicator": {"$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/applicator", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/applicator": true}, "$recursiveAnchor": true, "title": "Applicator vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"additionalItems": {"$recursiveRef": "#"}, "unevaluatedItems": {"$recursiveRef": "#"}, "items": {"anyOf": [{"$recursiveRef": "#"}, {"$ref": "#/$defs/schemaArray"}]}, "contains": {"$recursiveRef": "#"}, "additionalProperties": {"$recursiveRef": "#"}, "unevaluatedProperties": {"$recursiveRef": "#"}, "properties": {"type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {}}, "patternProperties": {"type": "object", "additionalProperties": {"$recursiveRef": "#"}, "propertyNames": {"format": "regex"}, "default": {}}, "dependentSchemas": {"type": "object", "additionalProperties": {"$recursiveRef": "#"}}, "propertyNames": {"$recursiveRef": "#"}, "if": {"$recursiveRef": "#"}, "then": {"$recursiveRef": "#"}, "else": {"$recursiveRef": "#"}, "allOf": {"$ref": "#/$defs/schemaArray"}, "anyOf": {"$ref": "#/$defs/schemaArray"}, "oneOf": {"$ref": "#/$defs/schemaArray"}, "not": {"$recursiveRef": "#"}}, "$defs": {"schemaArray": {"type": "array", "minItems": 1, "items": {"$recursiveRef": "#"}}}}, "https://json-schema.org/draft/2019-09/meta/meta-data": {"$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/meta-data": true}, "$recursiveAnchor": true, "title": "Meta-data vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"title": {"type": "string"}, "description": {"type": "string"}, "default": true, "deprecated": {"type": "boolean", "default": false}, "readOnly": {"type": "boolean", "default": false}, "writeOnly": {"type": "boolean", "default": false}, "examples": {"type": "array", "items": true}}}, "https://json-schema.org/draft/2019-09/meta/core": {"$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/core", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/core": true}, "$recursiveAnchor": true, "title": "Core vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"$id": {"type": "string", "format": "uri-reference", "$comment": "Non-empty fragments not allowed.", "pattern": "^[^#]*#?$"}, "$schema": {"type": "string", "format": "uri"}, "$anchor": {"type": "string", "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$"}, "$ref": {"type": "string", "format": "uri-reference"}, "$recursiveRef": {"type": "string", "format": "uri-reference"}, "$recursiveAnchor": {"type": "boolean", "default": false}, "$vocabulary": {"type": "object", "propertyNames": {"type": "string", "format": "uri"}, "additionalProperties": {"type": "boolean"}}, "$comment": {"type": "string"}, "$defs": {"type": "object", "additionalProperties": {"$recursiveRef": "#"}, "default": {}}}}, "https://json-schema.org/draft/2019-09/meta/validation": {"$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/validation", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/validation": true}, "$recursiveAnchor": true, "title": "Validation vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"multipleOf": {"type": "number", "exclusiveMinimum": 0}, "maximum": {"type": "number"}, "exclusiveMaximum": {"type": "number"}, "minimum": {"type": "number"}, "exclusiveMinimum": {"type": "number"}, "maxLength": {"$ref": "#/$defs/nonNegativeInteger"}, "minLength": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "pattern": {"type": "string", "format": "regex"}, "maxItems": {"$ref": "#/$defs/nonNegativeInteger"}, "minItems": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "uniqueItems": {"type": "boolean", "default": false}, "maxContains": {"$ref": "#/$defs/nonNegativeInteger"}, "minContains": {"$ref": "#/$defs/nonNegativeInteger", "default": 1}, "maxProperties": {"$ref": "#/$defs/nonNegativeInteger"}, "minProperties": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, "required": {"$ref": "#/$defs/stringArray"}, "dependentRequired": {"type": "object", "additionalProperties": {"$ref": "#/$defs/stringArray"}}, "const": true, "enum": {"type": "array", "items": true}, "type": {"anyOf": [{"$ref": "#/$defs/simpleTypes"}, {"type": "array", "items": {"$ref": "#/$defs/simpleTypes"}, "minItems": 1, "uniqueItems": true}]}}, "$defs": {"nonNegativeInteger": {"type": "integer", "minimum": 0}, "nonNegativeIntegerDefault0": {"$ref": "#/$defs/nonNegativeInteger", "default": 0}, "simpleTypes": {"enum": ["array", "boolean", "integer", "null", "number", "object", "string"]}, "stringArray": {"type": "array", "items": {"type": "string"}, "uniqueItems": true, "default": []}}}, "https://json-schema.org/draft/2019-09/meta/hyper-schema": {"$schema": "https://json-schema.org/draft/2019-09/hyper-schema", "$id": "https://json-schema.org/draft/2019-09/meta/hyper-schema", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/hyper-schema": true}, "$recursiveAnchor": true, "title": "JSON Hyper-Schema Vocabulary Schema", "type": ["object", "boolean"], "properties": {"base": {"type": "string", "format": "uri-template"}, "links": {"type": "array", "items": {"$ref": "https://json-schema.org/draft/2019-09/links"}}}, "links": [{"rel": "self", "href": "{+%24id}"}]}, "https://json-schema.org/draft/2019-09/meta/format": {"$schema": "https://json-schema.org/draft/2019-09/schema", "$id": "https://json-schema.org/draft/2019-09/meta/format", "$vocabulary": {"https://json-schema.org/draft/2019-09/vocab/format": true}, "$recursiveAnchor": true, "title": "Format vocabulary meta-schema", "type": ["object", "boolean"], "properties": {"format": {"type": "string"}}}} diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/validators.py b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/validators.py index 1dc420c70..2e33c4049 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/validators.py +++ b/conda_lock/_vendor/poetry/core/_vendor/jsonschema/validators.py @@ -1,105 +1,67 @@ """ Creation and extension of validators, with implementations for existing drafts. """ -from __future__ import division - +from __future__ import annotations + +from collections import deque +from collections.abc import Sequence +from functools import lru_cache +from operator import methodcaller +from urllib.parse import unquote, urldefrag, urljoin, urlsplit +from urllib.request import urlopen from warnings import warn import contextlib import json -import numbers +import reprlib +import typing +import warnings -from six import add_metaclass +from pyrsistent import m +import attr from jsonschema import ( + _format, _legacy_validators, _types, _utils, _validators, exceptions, ) -from jsonschema.compat import ( - Sequence, - int_types, - iteritems, - lru_cache, - str_types, - unquote, - urldefrag, - urljoin, - urlopen, - urlsplit, -) - -# Sigh. https://gitlab.com/pycqa/flake8/issues/280 -# https://github.com/pyga/ebb-lint/issues/7 -# Imported for backwards compatibility. -from jsonschema.exceptions import ErrorTree -ErrorTree - - -class _DontDoThat(Exception): - """ - Raised when a Validators with non-default type checker is misused. - - Asking one for DEFAULT_TYPES doesn't make sense, since type checkers - exist for the unrepresentable cases where DEFAULT_TYPES can't - represent the type relationship. - """ - - def __str__(self): - return "DEFAULT_TYPES cannot be used on Validators using TypeCheckers" - - -validators = {} -meta_schemas = _utils.URIDict() - - -def _generate_legacy_type_checks(types=()): - """ - Generate newer-style type checks out of JSON-type-name-to-type mappings. - - Arguments: - - types (dict): - A mapping of type names to their Python types +_UNSET = _utils.Unset() - Returns: - - A dictionary of definitions to pass to `TypeChecker` - """ - types = dict(types) - - def gen_type_check(pytypes): - pytypes = _utils.flatten(pytypes) - - def type_check(checker, instance): - if isinstance(instance, bool): - if bool not in pytypes: - return False - return isinstance(instance, pytypes) +_VALIDATORS: dict[str, typing.Any] = {} +_META_SCHEMAS = _utils.URIDict() +_VOCABULARIES: list[tuple[str, typing.Any]] = [] - return type_check - definitions = {} - for typename, pytypes in iteritems(types): - definitions[typename] = gen_type_check(pytypes) - - return definitions - - -_DEPRECATED_DEFAULT_TYPES = { - u"array": list, - u"boolean": bool, - u"integer": int_types, - u"null": type(None), - u"number": numbers.Number, - u"object": dict, - u"string": str_types, -} -_TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES = _types.TypeChecker( - type_checkers=_generate_legacy_type_checks(_DEPRECATED_DEFAULT_TYPES), -) +def __getattr__(name): + if name == "ErrorTree": + warnings.warn( + "Importing ErrorTree from jsonschema.validators is deprecated. " + "Instead import it from jsonschema.exceptions.", + DeprecationWarning, + stacklevel=2, + ) + from jsonschema.exceptions import ErrorTree + return ErrorTree + elif name == "validators": + warnings.warn( + "Accessing jsonschema.validators.validators is deprecated. " + "Use jsonschema.validators.validator_for with a given schema.", + DeprecationWarning, + stacklevel=2, + ) + return _VALIDATORS + elif name == "meta_schemas": + warnings.warn( + "Accessing jsonschema.validators.meta_schemas is deprecated. " + "Use jsonschema.validators.validator_for with a given schema.", + DeprecationWarning, + stacklevel=2, + ) + return _META_SCHEMAS + raise AttributeError(f"module {__name__} has no attribute {name}") def validates(version): @@ -107,7 +69,7 @@ def validates(version): Register the decorated validator for a ``version`` of the specification. Registered validators and their meta schemas will be considered when - parsing ``$schema`` properties' URIs. + parsing :kw:`$schema` keywords' URIs. Arguments: @@ -117,63 +79,55 @@ def validates(version): Returns: - collections.Callable: + collections.abc.Callable: a class decorator to decorate the validator with the version """ def _validates(cls): - validators[version] = cls + _VALIDATORS[version] = cls meta_schema_id = cls.ID_OF(cls.META_SCHEMA) - if meta_schema_id: - meta_schemas[meta_schema_id] = cls + _META_SCHEMAS[meta_schema_id] = cls return cls return _validates -def _DEFAULT_TYPES(self): - if self._CREATED_WITH_DEFAULT_TYPES is None: - raise _DontDoThat() - - warn( - ( - "The DEFAULT_TYPES attribute is deprecated. " - "See the type checker attached to this validator instead." - ), - DeprecationWarning, - stacklevel=2, - ) - return self._DEFAULT_TYPES - - -class _DefaultTypesDeprecatingMetaClass(type): - DEFAULT_TYPES = property(_DEFAULT_TYPES) - - def _id_of(schema): + """ + Return the ID of a schema for recent JSON Schema drafts. + """ if schema is True or schema is False: - return u"" - return schema.get(u"$id", u"") + return "" + return schema.get("$id", "") + + +def _store_schema_list(): + if not _VOCABULARIES: + _VOCABULARIES.extend(_utils.load_schema("vocabularies").items()) + return [ + (id, validator.META_SCHEMA) for id, validator in _META_SCHEMAS.items() + ] + _VOCABULARIES def create( meta_schema, validators=(), version=None, - default_types=None, - type_checker=None, + type_checker=_types.draft202012_type_checker, + format_checker=_format.draft202012_format_checker, id_of=_id_of, + applicable_validators=methodcaller("items"), ): """ Create a new validator class. Arguments: - meta_schema (collections.Mapping): + meta_schema (collections.abc.Mapping): the meta schema for the new validator class - validators (collections.Mapping): + validators (collections.abc.Mapping): a mapping from names to callables, where each callable will validate the schema property with the given name. @@ -196,112 +150,120 @@ def create( type_checker (jsonschema.TypeChecker): - a type checker, used when applying the :validator:`type` validator. + a type checker, used when applying the :kw:`type` keyword. If unprovided, a `jsonschema.TypeChecker` will be created with a set of default types typical of JSON Schema drafts. - default_types (collections.Mapping): - - .. deprecated:: 3.0.0 + format_checker (jsonschema.FormatChecker): - Please use the type_checker argument instead. + a format checker, used when applying the :kw:`format` keyword. - If set, it provides mappings of JSON types to Python types - that will be converted to functions and redefined in this - object's `jsonschema.TypeChecker`. + If unprovided, a `jsonschema.FormatChecker` will be created + with a set of default formats typical of JSON Schema drafts. - id_of (collections.Callable): + id_of (collections.abc.Callable): A function that given a schema, returns its ID. + applicable_validators (collections.abc.Callable): + + A function that given a schema, returns the list of + applicable validators (validation keywords and callables) + which will be used to validate the instance. + Returns: - a new `jsonschema.IValidator` class + a new `jsonschema.protocols.Validator` class """ + # preemptively don't shadow the `Validator.format_checker` local + format_checker_arg = format_checker - if default_types is not None: - if type_checker is not None: - raise TypeError( - "Do not specify default_types when providing a type checker.", - ) - _created_with_default_types = True - warn( - ( - "The default_types argument is deprecated. " - "Use the type_checker argument instead." - ), - DeprecationWarning, - stacklevel=2, - ) - type_checker = _types.TypeChecker( - type_checkers=_generate_legacy_type_checks(default_types), - ) - else: - default_types = _DEPRECATED_DEFAULT_TYPES - if type_checker is None: - _created_with_default_types = False - type_checker = _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES - elif type_checker is _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES: - _created_with_default_types = False - else: - _created_with_default_types = None - - @add_metaclass(_DefaultTypesDeprecatingMetaClass) - class Validator(object): + @attr.s + class Validator: VALIDATORS = dict(validators) META_SCHEMA = dict(meta_schema) TYPE_CHECKER = type_checker + FORMAT_CHECKER = format_checker_arg ID_OF = staticmethod(id_of) - DEFAULT_TYPES = property(_DEFAULT_TYPES) - _DEFAULT_TYPES = dict(default_types) - _CREATED_WITH_DEFAULT_TYPES = _created_with_default_types + schema = attr.ib(repr=reprlib.repr) + resolver = attr.ib(default=None, repr=False) + format_checker = attr.ib(default=None) + + def __init_subclass__(cls): + warnings.warn( + ( + "Subclassing validator classes is not intended to " + "be part of their public API. A future version " + "will make doing so an error, as the behavior of " + "subclasses isn't guaranteed to stay the same " + "between releases of jsonschema. Instead, prefer " + "composition of validators, wrapping them in an object " + "owned entirely by the downstream library." + ), + DeprecationWarning, + stacklevel=2, + ) - def __init__( - self, - schema, - types=(), - resolver=None, - format_checker=None, - ): - if types: - warn( - ( - "The types argument is deprecated. Provide " - "a type_checker to jsonschema.validators.extend " - "instead." - ), - DeprecationWarning, - stacklevel=2, + def __attrs_post_init__(self): + if self.resolver is None: + self.resolver = RefResolver.from_schema( + self.schema, + id_of=id_of, ) - self.TYPE_CHECKER = self.TYPE_CHECKER.redefine_many( - _generate_legacy_type_checks(types), - ) + @classmethod + def check_schema(cls, schema, format_checker=_UNSET): + Validator = validator_for(cls.META_SCHEMA, default=cls) + if format_checker is _UNSET: + format_checker = Validator.FORMAT_CHECKER + validator = Validator( + schema=cls.META_SCHEMA, + format_checker=format_checker, + ) + for error in validator.iter_errors(schema): + raise exceptions.SchemaError.create_from(error) - if resolver is None: - resolver = RefResolver.from_schema(schema, id_of=id_of) + def evolve(self, **changes): + # Essentially reproduces attr.evolve, but may involve instantiating + # a different class than this one. + cls = self.__class__ - self.resolver = resolver - self.format_checker = format_checker - self.schema = schema + schema = changes.setdefault("schema", self.schema) + NewValidator = validator_for(schema, default=cls) - @classmethod - def check_schema(cls, schema): - for error in cls(cls.META_SCHEMA).iter_errors(schema): - raise exceptions.SchemaError.create_from(error) + for field in attr.fields(cls): + if not field.init: + continue + attr_name = field.name # To deal with private attributes. + init_name = attr_name if attr_name[0] != "_" else attr_name[1:] + if init_name not in changes: + changes[init_name] = getattr(self, attr_name) + + return NewValidator(**changes) def iter_errors(self, instance, _schema=None): - if _schema is None: + if _schema is not None: + warnings.warn( + ( + "Passing a schema to Validator.iter_errors " + "is deprecated and will be removed in a future " + "release. Call validator.evolve(schema=new_schema)." + "iter_errors(...) instead." + ), + DeprecationWarning, + stacklevel=2, + ) + else: _schema = self.schema if _schema is True: return elif _schema is False: yield exceptions.ValidationError( - "False schema does not allow %r" % (instance,), + f"False schema does not allow {instance!r}", validator=None, validator_value=None, instance=instance, @@ -313,13 +275,7 @@ def iter_errors(self, instance, _schema=None): if scope: self.resolver.push_scope(scope) try: - ref = _schema.get(u"$ref") - if ref is not None: - validators = [(u"$ref", ref)] - else: - validators = iteritems(_schema) - - for k, v in validators: + for k, v in applicable_validators(_schema): validator = self.VALIDATORS.get(k) if validator is None: continue @@ -332,8 +288,9 @@ def iter_errors(self, instance, _schema=None): validator_value=v, instance=instance, schema=_schema, + type_checker=self.TYPE_CHECKER, ) - if k != u"$ref": + if k not in {"if", "$ref"}: error.schema_path.appendleft(k) yield error finally: @@ -341,7 +298,7 @@ def iter_errors(self, instance, _schema=None): self.resolver.pop_scope() def descend(self, instance, schema, path=None, schema_path=None): - for error in self.iter_errors(instance, schema): + for error in self.evolve(schema=schema).iter_errors(instance): if path is not None: error.path.appendleft(path) if schema_path is not None: @@ -359,27 +316,47 @@ def is_type(self, instance, type): raise exceptions.UnknownType(type, instance, self.schema) def is_valid(self, instance, _schema=None): - error = next(self.iter_errors(instance, _schema), None) + if _schema is not None: + warnings.warn( + ( + "Passing a schema to Validator.is_valid is deprecated " + "and will be removed in a future release. Call " + "validator.evolve(schema=new_schema).is_valid(...) " + "instead." + ), + DeprecationWarning, + stacklevel=2, + ) + self = self.evolve(schema=_schema) + + error = next(self.iter_errors(instance), None) return error is None if version is not None: + safe = version.title().replace(" ", "").replace("-", "") + Validator.__name__ = Validator.__qualname__ = f"{safe}Validator" Validator = validates(version)(Validator) - Validator.__name__ = version.title().replace(" ", "") + "Validator" return Validator -def extend(validator, validators=(), version=None, type_checker=None): +def extend( + validator, + validators=(), + version=None, + type_checker=None, + format_checker=None, +): """ Create a new validator class by extending an existing one. Arguments: - validator (jsonschema.IValidator): + validator (jsonschema.protocols.Validator): an existing validator class - validators (collections.Mapping): + validators (collections.abc.Mapping): a mapping of new validator callables to extend with, whose structure is as in `create`. @@ -394,7 +371,7 @@ def extend(validator, validators=(), version=None, type_checker=None): If you wish to instead extend the behavior of a parent's validator callable, delegate and call it directly in the new validator function by retrieving it using - ``OldValidator.VALIDATORS["validator_name"]``. + ``OldValidator.VALIDATORS["validation_keyword_name"]``. version (str): @@ -402,14 +379,22 @@ def extend(validator, validators=(), version=None, type_checker=None): type_checker (jsonschema.TypeChecker): - a type checker, used when applying the :validator:`type` validator. + a type checker, used when applying the :kw:`type` keyword. If unprovided, the type checker of the extended - `jsonschema.IValidator` will be carried along.` + `jsonschema.protocols.Validator` will be carried along. + + format_checker (jsonschema.FormatChecker): + + a format checker, used when applying the :kw:`format` keyword. + + If unprovided, the format checker of the extended + `jsonschema.protocols.Validator` will be carried along. Returns: - a new `jsonschema.IValidator` class extending the one provided + a new `jsonschema.protocols.Validator` class extending the one + provided .. note:: Meta Schemas @@ -427,17 +412,14 @@ def extend(validator, validators=(), version=None, type_checker=None): if type_checker is None: type_checker = validator.TYPE_CHECKER - elif validator._CREATED_WITH_DEFAULT_TYPES: - raise TypeError( - "Cannot extend a validator created with default_types " - "with a type_checker. Update the validator to use a " - "type_checker when created." - ) + if format_checker is None: + format_checker = validator.FORMAT_CHECKER return create( meta_schema=validator.META_SCHEMA, validators=all_validators, version=version, type_checker=type_checker, + format_checker=format_checker, id_of=validator.ID_OF, ) @@ -445,151 +427,252 @@ def extend(validator, validators=(), version=None, type_checker=None): Draft3Validator = create( meta_schema=_utils.load_schema("draft3"), validators={ - u"$ref": _validators.ref, - u"additionalItems": _validators.additionalItems, - u"additionalProperties": _validators.additionalProperties, - u"dependencies": _legacy_validators.dependencies_draft3, - u"disallow": _legacy_validators.disallow_draft3, - u"divisibleBy": _validators.multipleOf, - u"enum": _validators.enum, - u"extends": _legacy_validators.extends_draft3, - u"format": _validators.format, - u"items": _legacy_validators.items_draft3_draft4, - u"maxItems": _validators.maxItems, - u"maxLength": _validators.maxLength, - u"maximum": _legacy_validators.maximum_draft3_draft4, - u"minItems": _validators.minItems, - u"minLength": _validators.minLength, - u"minimum": _legacy_validators.minimum_draft3_draft4, - u"pattern": _validators.pattern, - u"patternProperties": _validators.patternProperties, - u"properties": _legacy_validators.properties_draft3, - u"type": _legacy_validators.type_draft3, - u"uniqueItems": _validators.uniqueItems, + "$ref": _validators.ref, + "additionalItems": _validators.additionalItems, + "additionalProperties": _validators.additionalProperties, + "dependencies": _legacy_validators.dependencies_draft3, + "disallow": _legacy_validators.disallow_draft3, + "divisibleBy": _validators.multipleOf, + "enum": _validators.enum, + "extends": _legacy_validators.extends_draft3, + "format": _validators.format, + "items": _legacy_validators.items_draft3_draft4, + "maxItems": _validators.maxItems, + "maxLength": _validators.maxLength, + "maximum": _legacy_validators.maximum_draft3_draft4, + "minItems": _validators.minItems, + "minLength": _validators.minLength, + "minimum": _legacy_validators.minimum_draft3_draft4, + "pattern": _validators.pattern, + "patternProperties": _validators.patternProperties, + "properties": _legacy_validators.properties_draft3, + "type": _legacy_validators.type_draft3, + "uniqueItems": _validators.uniqueItems, }, type_checker=_types.draft3_type_checker, + format_checker=_format.draft3_format_checker, version="draft3", - id_of=lambda schema: schema.get(u"id", ""), + id_of=_legacy_validators.id_of_ignore_ref(property="id"), + applicable_validators=_legacy_validators.ignore_ref_siblings, ) Draft4Validator = create( meta_schema=_utils.load_schema("draft4"), validators={ - u"$ref": _validators.ref, - u"additionalItems": _validators.additionalItems, - u"additionalProperties": _validators.additionalProperties, - u"allOf": _validators.allOf, - u"anyOf": _validators.anyOf, - u"dependencies": _validators.dependencies, - u"enum": _validators.enum, - u"format": _validators.format, - u"items": _legacy_validators.items_draft3_draft4, - u"maxItems": _validators.maxItems, - u"maxLength": _validators.maxLength, - u"maxProperties": _validators.maxProperties, - u"maximum": _legacy_validators.maximum_draft3_draft4, - u"minItems": _validators.minItems, - u"minLength": _validators.minLength, - u"minProperties": _validators.minProperties, - u"minimum": _legacy_validators.minimum_draft3_draft4, - u"multipleOf": _validators.multipleOf, - u"not": _validators.not_, - u"oneOf": _validators.oneOf, - u"pattern": _validators.pattern, - u"patternProperties": _validators.patternProperties, - u"properties": _validators.properties, - u"required": _validators.required, - u"type": _validators.type, - u"uniqueItems": _validators.uniqueItems, + "$ref": _validators.ref, + "additionalItems": _validators.additionalItems, + "additionalProperties": _validators.additionalProperties, + "allOf": _validators.allOf, + "anyOf": _validators.anyOf, + "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, + "enum": _validators.enum, + "format": _validators.format, + "items": _legacy_validators.items_draft3_draft4, + "maxItems": _validators.maxItems, + "maxLength": _validators.maxLength, + "maxProperties": _validators.maxProperties, + "maximum": _legacy_validators.maximum_draft3_draft4, + "minItems": _validators.minItems, + "minLength": _validators.minLength, + "minProperties": _validators.minProperties, + "minimum": _legacy_validators.minimum_draft3_draft4, + "multipleOf": _validators.multipleOf, + "not": _validators.not_, + "oneOf": _validators.oneOf, + "pattern": _validators.pattern, + "patternProperties": _validators.patternProperties, + "properties": _validators.properties, + "required": _validators.required, + "type": _validators.type, + "uniqueItems": _validators.uniqueItems, }, type_checker=_types.draft4_type_checker, + format_checker=_format.draft4_format_checker, version="draft4", - id_of=lambda schema: schema.get(u"id", ""), + id_of=_legacy_validators.id_of_ignore_ref(property="id"), + applicable_validators=_legacy_validators.ignore_ref_siblings, ) Draft6Validator = create( meta_schema=_utils.load_schema("draft6"), validators={ - u"$ref": _validators.ref, - u"additionalItems": _validators.additionalItems, - u"additionalProperties": _validators.additionalProperties, - u"allOf": _validators.allOf, - u"anyOf": _validators.anyOf, - u"const": _validators.const, - u"contains": _validators.contains, - u"dependencies": _validators.dependencies, - u"enum": _validators.enum, - u"exclusiveMaximum": _validators.exclusiveMaximum, - u"exclusiveMinimum": _validators.exclusiveMinimum, - u"format": _validators.format, - u"items": _validators.items, - u"maxItems": _validators.maxItems, - u"maxLength": _validators.maxLength, - u"maxProperties": _validators.maxProperties, - u"maximum": _validators.maximum, - u"minItems": _validators.minItems, - u"minLength": _validators.minLength, - u"minProperties": _validators.minProperties, - u"minimum": _validators.minimum, - u"multipleOf": _validators.multipleOf, - u"not": _validators.not_, - u"oneOf": _validators.oneOf, - u"pattern": _validators.pattern, - u"patternProperties": _validators.patternProperties, - u"properties": _validators.properties, - u"propertyNames": _validators.propertyNames, - u"required": _validators.required, - u"type": _validators.type, - u"uniqueItems": _validators.uniqueItems, + "$ref": _validators.ref, + "additionalItems": _validators.additionalItems, + "additionalProperties": _validators.additionalProperties, + "allOf": _validators.allOf, + "anyOf": _validators.anyOf, + "const": _validators.const, + "contains": _legacy_validators.contains_draft6_draft7, + "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, + "enum": _validators.enum, + "exclusiveMaximum": _validators.exclusiveMaximum, + "exclusiveMinimum": _validators.exclusiveMinimum, + "format": _validators.format, + "items": _legacy_validators.items_draft6_draft7_draft201909, + "maxItems": _validators.maxItems, + "maxLength": _validators.maxLength, + "maxProperties": _validators.maxProperties, + "maximum": _validators.maximum, + "minItems": _validators.minItems, + "minLength": _validators.minLength, + "minProperties": _validators.minProperties, + "minimum": _validators.minimum, + "multipleOf": _validators.multipleOf, + "not": _validators.not_, + "oneOf": _validators.oneOf, + "pattern": _validators.pattern, + "patternProperties": _validators.patternProperties, + "properties": _validators.properties, + "propertyNames": _validators.propertyNames, + "required": _validators.required, + "type": _validators.type, + "uniqueItems": _validators.uniqueItems, }, type_checker=_types.draft6_type_checker, + format_checker=_format.draft6_format_checker, version="draft6", + id_of=_legacy_validators.id_of_ignore_ref(), + applicable_validators=_legacy_validators.ignore_ref_siblings, ) Draft7Validator = create( meta_schema=_utils.load_schema("draft7"), validators={ - u"$ref": _validators.ref, - u"additionalItems": _validators.additionalItems, - u"additionalProperties": _validators.additionalProperties, - u"allOf": _validators.allOf, - u"anyOf": _validators.anyOf, - u"const": _validators.const, - u"contains": _validators.contains, - u"dependencies": _validators.dependencies, - u"enum": _validators.enum, - u"exclusiveMaximum": _validators.exclusiveMaximum, - u"exclusiveMinimum": _validators.exclusiveMinimum, - u"format": _validators.format, - u"if": _validators.if_, - u"items": _validators.items, - u"maxItems": _validators.maxItems, - u"maxLength": _validators.maxLength, - u"maxProperties": _validators.maxProperties, - u"maximum": _validators.maximum, - u"minItems": _validators.minItems, - u"minLength": _validators.minLength, - u"minProperties": _validators.minProperties, - u"minimum": _validators.minimum, - u"multipleOf": _validators.multipleOf, - u"oneOf": _validators.oneOf, - u"not": _validators.not_, - u"pattern": _validators.pattern, - u"patternProperties": _validators.patternProperties, - u"properties": _validators.properties, - u"propertyNames": _validators.propertyNames, - u"required": _validators.required, - u"type": _validators.type, - u"uniqueItems": _validators.uniqueItems, + "$ref": _validators.ref, + "additionalItems": _validators.additionalItems, + "additionalProperties": _validators.additionalProperties, + "allOf": _validators.allOf, + "anyOf": _validators.anyOf, + "const": _validators.const, + "contains": _legacy_validators.contains_draft6_draft7, + "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, + "enum": _validators.enum, + "exclusiveMaximum": _validators.exclusiveMaximum, + "exclusiveMinimum": _validators.exclusiveMinimum, + "format": _validators.format, + "if": _validators.if_, + "items": _legacy_validators.items_draft6_draft7_draft201909, + "maxItems": _validators.maxItems, + "maxLength": _validators.maxLength, + "maxProperties": _validators.maxProperties, + "maximum": _validators.maximum, + "minItems": _validators.minItems, + "minLength": _validators.minLength, + "minProperties": _validators.minProperties, + "minimum": _validators.minimum, + "multipleOf": _validators.multipleOf, + "not": _validators.not_, + "oneOf": _validators.oneOf, + "pattern": _validators.pattern, + "patternProperties": _validators.patternProperties, + "properties": _validators.properties, + "propertyNames": _validators.propertyNames, + "required": _validators.required, + "type": _validators.type, + "uniqueItems": _validators.uniqueItems, }, type_checker=_types.draft7_type_checker, + format_checker=_format.draft7_format_checker, version="draft7", + id_of=_legacy_validators.id_of_ignore_ref(), + applicable_validators=_legacy_validators.ignore_ref_siblings, ) -_LATEST_VERSION = Draft7Validator +Draft201909Validator = create( + meta_schema=_utils.load_schema("draft2019-09"), + validators={ + "$recursiveRef": _legacy_validators.recursiveRef, + "$ref": _validators.ref, + "additionalItems": _validators.additionalItems, + "additionalProperties": _validators.additionalProperties, + "allOf": _validators.allOf, + "anyOf": _validators.anyOf, + "const": _validators.const, + "contains": _validators.contains, + "dependentRequired": _validators.dependentRequired, + "dependentSchemas": _validators.dependentSchemas, + "enum": _validators.enum, + "exclusiveMaximum": _validators.exclusiveMaximum, + "exclusiveMinimum": _validators.exclusiveMinimum, + "format": _validators.format, + "if": _validators.if_, + "items": _legacy_validators.items_draft6_draft7_draft201909, + "maxItems": _validators.maxItems, + "maxLength": _validators.maxLength, + "maxProperties": _validators.maxProperties, + "maximum": _validators.maximum, + "minItems": _validators.minItems, + "minLength": _validators.minLength, + "minProperties": _validators.minProperties, + "minimum": _validators.minimum, + "multipleOf": _validators.multipleOf, + "not": _validators.not_, + "oneOf": _validators.oneOf, + "pattern": _validators.pattern, + "patternProperties": _validators.patternProperties, + "properties": _validators.properties, + "propertyNames": _validators.propertyNames, + "required": _validators.required, + "type": _validators.type, + "unevaluatedItems": _legacy_validators.unevaluatedItems_draft2019, + "unevaluatedProperties": _validators.unevaluatedProperties, + "uniqueItems": _validators.uniqueItems, + }, + type_checker=_types.draft201909_type_checker, + format_checker=_format.draft201909_format_checker, + version="draft2019-09", +) + +Draft202012Validator = create( + meta_schema=_utils.load_schema("draft2020-12"), + validators={ + "$dynamicRef": _validators.dynamicRef, + "$ref": _validators.ref, + "additionalItems": _validators.additionalItems, + "additionalProperties": _validators.additionalProperties, + "allOf": _validators.allOf, + "anyOf": _validators.anyOf, + "const": _validators.const, + "contains": _validators.contains, + "dependentRequired": _validators.dependentRequired, + "dependentSchemas": _validators.dependentSchemas, + "enum": _validators.enum, + "exclusiveMaximum": _validators.exclusiveMaximum, + "exclusiveMinimum": _validators.exclusiveMinimum, + "format": _validators.format, + "if": _validators.if_, + "items": _validators.items, + "maxItems": _validators.maxItems, + "maxLength": _validators.maxLength, + "maxProperties": _validators.maxProperties, + "maximum": _validators.maximum, + "minItems": _validators.minItems, + "minLength": _validators.minLength, + "minProperties": _validators.minProperties, + "minimum": _validators.minimum, + "multipleOf": _validators.multipleOf, + "not": _validators.not_, + "oneOf": _validators.oneOf, + "pattern": _validators.pattern, + "patternProperties": _validators.patternProperties, + "prefixItems": _validators.prefixItems, + "properties": _validators.properties, + "propertyNames": _validators.propertyNames, + "required": _validators.required, + "type": _validators.type, + "unevaluatedItems": _validators.unevaluatedItems, + "unevaluatedProperties": _validators.unevaluatedProperties, + "uniqueItems": _validators.uniqueItems, + }, + type_checker=_types.draft202012_type_checker, + format_checker=_format.draft202012_format_checker, + version="draft2020-12", +) + +_LATEST_VERSION = Draft202012Validator -class RefResolver(object): +class RefResolver: """ Resolve JSON References. @@ -637,7 +720,7 @@ def __init__( self, base_uri, referrer, - store=(), + store=m(), cache_remote=True, handlers=(), urljoin_cache=None, @@ -653,11 +736,13 @@ def __init__( self.handlers = dict(handlers) self._scopes_stack = [base_uri] - self.store = _utils.URIDict( - (id, validator.META_SCHEMA) - for id, validator in iteritems(meta_schemas) - ) + + self.store = _utils.URIDict(_store_schema_list()) self.store.update(store) + self.store.update( + (schema["$id"], schema) + for schema in store.values() if "$id" in schema + ) self.store[base_uri] = referrer self._urljoin_cache = urljoin_cache @@ -679,7 +764,7 @@ def from_schema(cls, schema, id_of=_id_of, *args, **kwargs): `RefResolver` """ - return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) + return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) # noqa: B026, E501 def push_scope(self, scope): """ @@ -708,7 +793,7 @@ def pop_scope(self): raise exceptions.RefResolutionError( "Failed to pop the scope from an empty stack. " "`pop_scope()` should only be called once for every " - "`push_scope()`" + "`push_scope()`", ) @property @@ -730,7 +815,15 @@ def base_uri(self): def in_scope(self, scope): """ Temporarily enter the given scope for the duration of the context. + + .. deprecated:: v4.0.0 """ + warnings.warn( + "jsonschema.RefResolver.in_scope is deprecated and will be " + "removed in a future release.", + DeprecationWarning, + stacklevel=3, + ) self.push_scope(scope) try: yield @@ -758,18 +851,55 @@ def resolving(self, ref): finally: self.pop_scope() + def _find_in_referrer(self, key): + return self._get_subschemas_cache()[key] + + @lru_cache() # noqa: B019 + def _get_subschemas_cache(self): + cache = {key: [] for key in _SUBSCHEMAS_KEYWORDS} + for keyword, subschema in _search_schema( + self.referrer, _match_subschema_keywords, + ): + cache[keyword].append(subschema) + return cache + + @lru_cache() # noqa: B019 + def _find_in_subschemas(self, url): + subschemas = self._get_subschemas_cache()["$id"] + if not subschemas: + return None + uri, fragment = urldefrag(url) + for subschema in subschemas: + target_uri = self._urljoin_cache( + self.resolution_scope, subschema["$id"], + ) + if target_uri.rstrip("/") == uri.rstrip("/"): + if fragment: + subschema = self.resolve_fragment(subschema, fragment) + self.store[url] = subschema + return url, subschema + return None + def resolve(self, ref): """ Resolve the given reference. """ - url = self._urljoin_cache(self.resolution_scope, ref) + url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/") + + match = self._find_in_subschemas(url) + if match is not None: + return match + return url, self._remote_cache(url) def resolve_from_url(self, url): """ - Resolve the given remote URL. + Resolve the given URL. """ url, fragment = urldefrag(url) + if not url: + url = self.base_uri + try: document = self.store[url] except KeyError: @@ -795,11 +925,31 @@ def resolve_fragment(self, document, fragment): a URI fragment to resolve within it """ - fragment = fragment.lstrip(u"/") - parts = unquote(fragment).split(u"/") if fragment else [] + fragment = fragment.lstrip("/") + + if not fragment: + return document + + if document is self.referrer: + find = self._find_in_referrer + else: + + def find(key): + yield from _search_schema(document, _match_keyword(key)) + for keyword in ["$anchor", "$dynamicAnchor"]: + for subschema in find(keyword): + if fragment == subschema[keyword]: + return subschema + for keyword in ["id", "$id"]: + for subschema in find(keyword): + if "#" + fragment == subschema[keyword]: + return subschema + + # Resolve via path + parts = unquote(fragment).split("/") if fragment else [] for part in parts: - part = part.replace(u"~1", u"/").replace(u"~0", u"~") + part = part.replace("~1", "/").replace("~0", "~") if isinstance(document, Sequence): # Array indexes should be turned into integers @@ -811,7 +961,7 @@ def resolve_fragment(self, document, fragment): document = document[part] except (TypeError, LookupError): raise exceptions.RefResolutionError( - "Unresolvable JSON pointer: %r" % fragment + f"Unresolvable JSON pointer: {fragment!r}", ) return document @@ -854,7 +1004,7 @@ def resolve_remote(self, uri): if scheme in self.handlers: result = self.handlers[scheme](uri) - elif scheme in [u"http", u"https"] and requests: + elif scheme in ["http", "https"] and requests: # Requests has support for detecting the correct encoding of # json over http result = requests.get(uri).json() @@ -868,6 +1018,35 @@ def resolve_remote(self, uri): return result +_SUBSCHEMAS_KEYWORDS = ("$id", "id", "$anchor", "$dynamicAnchor") + + +def _match_keyword(keyword): + + def matcher(value): + if keyword in value: + yield value + + return matcher + + +def _match_subschema_keywords(value): + for keyword in _SUBSCHEMAS_KEYWORDS: + if keyword in value: + yield keyword, value + + +def _search_schema(schema, matcher): + """Breadth-first search routine.""" + values = deque([schema]) + while values: + value = values.pop() + if not isinstance(value, dict): + continue + yield from matcher(value) + values.extendleft(value.values()) + + def validate(instance, schema, cls=None, *args, **kwargs): """ Validate an instance under the given schema. @@ -881,10 +1060,11 @@ def validate(instance, schema, cls=None, *args, **kwargs): itself valid, since not doing so can lead to less obvious error messages and fail in less obvious or consistent ways. - If you know you have a valid schema already, especially if you - intend to validate multiple instances with the same schema, you - likely would prefer using the `IValidator.validate` method directly - on a specific validator (e.g. ``Draft7Validator.validate``). + If you know you have a valid schema already, especially + if you intend to validate multiple instances with + the same schema, you likely would prefer using the + `jsonschema.protocols.Validator.validate` method directly on a + specific validator (e.g. ``Draft20212Validator.validate``). Arguments: @@ -897,28 +1077,30 @@ def validate(instance, schema, cls=None, *args, **kwargs): The schema to validate with - cls (IValidator): + cls (jsonschema.protocols.Validator): The class that will be used to validate the instance. If the ``cls`` argument is not provided, two things will happen in accordance with the specification. First, if the schema has a - :validator:`$schema` property containing a known meta-schema [#]_ - then the proper validator will be used. The specification recommends - that all schemas contain :validator:`$schema` properties for this - reason. If no :validator:`$schema` property is found, the default - validator class is the latest released draft. + :kw:`$schema` keyword containing a known meta-schema [#]_ then the + proper validator will be used. The specification recommends that + all schemas contain :kw:`$schema` properties for this reason. If no + :kw:`$schema` property is found, the default validator class is the + latest released draft. Any other provided positional and keyword arguments will be passed on when instantiating the ``cls``. Raises: - `jsonschema.exceptions.ValidationError` if the instance - is invalid + `jsonschema.exceptions.ValidationError`: + + if the instance is invalid - `jsonschema.exceptions.SchemaError` if the schema itself - is invalid + `jsonschema.exceptions.SchemaError`: + + if the schema itself is invalid .. rubric:: Footnotes .. [#] known by a validator registered with @@ -934,16 +1116,16 @@ def validate(instance, schema, cls=None, *args, **kwargs): raise error -def validator_for(schema, default=_LATEST_VERSION): +def validator_for(schema, default=_UNSET): """ Retrieve the validator class appropriate for validating the given schema. - Uses the :validator:`$schema` property that should be present in the - given schema to look up the appropriate validator class. + Uses the :kw:`$schema` keyword that should be present in the given + schema to look up the appropriate validator class. Arguments: - schema (collections.Mapping or bool): + schema (collections.abc.Mapping or bool): the schema to look at @@ -955,16 +1137,20 @@ def validator_for(schema, default=_LATEST_VERSION): If unprovided, the default is to return the latest supported draft. """ - if schema is True or schema is False or u"$schema" not in schema: - return default - if schema[u"$schema"] not in meta_schemas: - warn( - ( - "The metaschema specified by $schema was not found. " - "Using the latest draft to validate, but this will raise " - "an error in the future." - ), - DeprecationWarning, - stacklevel=2, - ) - return meta_schemas.get(schema[u"$schema"], _LATEST_VERSION) + + DefaultValidator = _LATEST_VERSION if default is _UNSET else default + + if schema is True or schema is False or "$schema" not in schema: + return DefaultValidator + if schema["$schema"] not in _META_SCHEMAS: + if default is _UNSET: + warn( + ( + "The metaschema specified by $schema was not found. " + "Using the latest draft to validate, but this will raise " + "an error in the future." + ), + DeprecationWarning, + stacklevel=2, + ) + return _META_SCHEMAS.get(schema["$schema"], DefaultValidator) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark-parser.LICENSE b/conda_lock/_vendor/poetry/core/_vendor/lark/LICENSE similarity index 99% rename from conda_lock/_vendor/poetry/core/_vendor/lark-parser.LICENSE rename to conda_lock/_vendor/poetry/core/_vendor/lark/LICENSE index efcb9665f..aaf210b1d 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark-parser.LICENSE +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/LICENSE @@ -16,4 +16,3 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/lark/__init__.py index 8ddab96a8..d8d0c88a0 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/__init__.py @@ -1,9 +1,38 @@ -from .tree import Tree -from .visitors import Transformer, Visitor, v_args, Discard -from .visitors import InlineTransformer, inline_args # XXX Deprecated -from .exceptions import (ParseError, LexError, GrammarError, UnexpectedToken, - UnexpectedInput, UnexpectedCharacters, LarkError) -from .lexer import Token +from .exceptions import ( + GrammarError, + LarkError, + LexError, + ParseError, + UnexpectedCharacters, + UnexpectedEOF, + UnexpectedInput, + UnexpectedToken, +) from .lark import Lark +from .lexer import Token +from .tree import ParseTree, Tree +from .utils import logger +from .visitors import Discard, Transformer, Transformer_NonRecursive, Visitor, v_args + +__version__: str = "1.1.4" -__version__ = "0.9.0" +__all__ = ( + "GrammarError", + "LarkError", + "LexError", + "ParseError", + "UnexpectedCharacters", + "UnexpectedEOF", + "UnexpectedInput", + "UnexpectedToken", + "Lark", + "Token", + "ParseTree", + "Tree", + "logger", + "Discard", + "Transformer", + "Transformer_NonRecursive", + "Visitor", + "v_args", +) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/__pyinstaller/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/lark/__pyinstaller/__init__.py index fa02fc923..9da62a333 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/__pyinstaller/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/__pyinstaller/__init__.py @@ -3,4 +3,4 @@ import os def get_hook_dirs(): - return [os.path.dirname(__file__)] \ No newline at end of file + return [os.path.dirname(__file__)] diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/ast_utils.py b/conda_lock/_vendor/poetry/core/_vendor/lark/ast_utils.py new file mode 100644 index 000000000..0ceee98f6 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/ast_utils.py @@ -0,0 +1,59 @@ +""" + Module of utilities for transforming a lark.Tree into a custom Abstract Syntax Tree +""" + +import inspect, re +import types +from typing import Optional, Callable + +from lark import Transformer, v_args + +class Ast: + """Abstract class + + Subclasses will be collected by `create_transformer()` + """ + pass + +class AsList: + """Abstract class + + Subclasses will be instantiated with the parse results as a single list, instead of as arguments. + """ + +class WithMeta: + """Abstract class + + Subclasses will be instantiated with the Meta instance of the tree. (see ``v_args`` for more detail) + """ + pass + +def camel_to_snake(name): + return re.sub(r'(? Transformer: + """Collects `Ast` subclasses from the given module, and creates a Lark transformer that builds the AST. + + For each class, we create a corresponding rule in the transformer, with a matching name. + CamelCase names will be converted into snake_case. Example: "CodeBlock" -> "code_block". + + Classes starting with an underscore (`_`) will be skipped. + + Parameters: + ast_module: A Python module containing all the subclasses of ``ast_utils.Ast`` + transformer (Optional[Transformer]): An initial transformer. Its attributes may be overwritten. + decorator_factory (Callable): An optional callable accepting two booleans, inline, and meta, + and returning a decorator for the methods of ``transformer``. (default: ``v_args``). + """ + t = transformer or Transformer() + + for name, obj in inspect.getmembers(ast_module): + if not name.startswith('_') and inspect.isclass(obj): + if issubclass(obj, Ast): + wrapper = decorator_factory(inline=not issubclass(obj, AsList), meta=issubclass(obj, WithMeta)) + obj = wrapper(obj).__get__(t) + setattr(t, camel_to_snake(name), obj) + + return t diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/common.py b/conda_lock/_vendor/poetry/core/_vendor/lark/common.py index c44f9cef3..d716add7e 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/common.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/common.py @@ -1,29 +1,82 @@ +from copy import deepcopy +import sys +from types import ModuleType +from typing import Callable, Collection, Dict, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from .lark import PostLex + from .lexer import Lexer + from typing import Union, Type + if sys.version_info >= (3, 8): + from typing import Literal + else: + from typing_extensions import Literal + if sys.version_info >= (3, 10): + from typing import TypeAlias + else: + from typing_extensions import TypeAlias + from .utils import Serialize -from .lexer import TerminalDef +from .lexer import TerminalDef, Token ###{standalone +_ParserArgType: 'TypeAlias' = 'Literal["earley", "lalr", "cyk", "auto"]' +_LexerArgType: 'TypeAlias' = 'Union[Literal["auto", "basic", "contextual", "dynamic", "dynamic_complete"], Type[Lexer]]' +_Callback = Callable[[Token], Token] + class LexerConf(Serialize): - __serialize_fields__ = 'tokens', 'ignore', 'g_regex_flags' + __serialize_fields__ = 'terminals', 'ignore', 'g_regex_flags', 'use_bytes', 'lexer_type' __serialize_namespace__ = TerminalDef, - def __init__(self, tokens, ignore=(), postlex=None, callbacks=None, g_regex_flags=0): - self.tokens = tokens + terminals: Collection[TerminalDef] + re_module: ModuleType + ignore: Collection[str] + postlex: 'Optional[PostLex]' + callbacks: Dict[str, _Callback] + g_regex_flags: int + skip_validation: bool + use_bytes: bool + lexer_type: Optional[_LexerArgType] + + def __init__(self, terminals: Collection[TerminalDef], re_module: ModuleType, ignore: Collection[str]=(), postlex: 'Optional[PostLex]'=None, callbacks: Optional[Dict[str, _Callback]]=None, g_regex_flags: int=0, skip_validation: bool=False, use_bytes: bool=False): + self.terminals = terminals + self.terminals_by_name = {t.name: t for t in self.terminals} + assert len(self.terminals) == len(self.terminals_by_name) self.ignore = ignore self.postlex = postlex self.callbacks = callbacks or {} self.g_regex_flags = g_regex_flags + self.re_module = re_module + self.skip_validation = skip_validation + self.use_bytes = use_bytes + self.lexer_type = None def _deserialize(self): - self.callbacks = {} # TODO + self.terminals_by_name = {t.name: t for t in self.terminals} + + def __deepcopy__(self, memo=None): + return type(self)( + deepcopy(self.terminals, memo), + self.re_module, + deepcopy(self.ignore, memo), + deepcopy(self.postlex, memo), + deepcopy(self.callbacks, memo), + deepcopy(self.g_regex_flags, memo), + deepcopy(self.skip_validation, memo), + deepcopy(self.use_bytes, memo), + ) -###} -class ParserConf: +class ParserConf(Serialize): + __serialize_fields__ = 'rules', 'start', 'parser_type' + def __init__(self, rules, callbacks, start): assert isinstance(start, list) self.rules = rules self.callbacks = callbacks self.start = start + self.parser_type = None +###} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/exceptions.py b/conda_lock/_vendor/poetry/core/_vendor/lark/exceptions.py index 1c5e533e4..35b986af4 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/exceptions.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/exceptions.py @@ -1,119 +1,292 @@ -from .utils import STRING_TYPE +from .utils import logger, NO_VALUE +from typing import Mapping, Iterable, Callable, Union, TypeVar, Tuple, Any, List, Set, Optional, Collection, TYPE_CHECKING + +if TYPE_CHECKING: + from .lexer import Token + from .parsers.lalr_interactive_parser import InteractiveParser + from .tree import Tree ###{standalone + class LarkError(Exception): pass + +class ConfigurationError(LarkError, ValueError): + pass + + +def assert_config(value, options: Collection, msg='Got %r, expected one of %s'): + if value not in options: + raise ConfigurationError(msg % (value, options)) + + class GrammarError(LarkError): pass + class ParseError(LarkError): pass + class LexError(LarkError): pass -class UnexpectedEOF(ParseError): - def __init__(self, expected): - self.expected = expected +T = TypeVar('T') - message = ("Unexpected end-of-input. Expected one of: \n\t* %s\n" % '\n\t* '.join(x.name for x in self.expected)) - super(UnexpectedEOF, self).__init__(message) +class UnexpectedInput(LarkError): + """UnexpectedInput Error. + Used as a base class for the following exceptions: -class UnexpectedInput(LarkError): + - ``UnexpectedCharacters``: The lexer encountered an unexpected string + - ``UnexpectedToken``: The parser received an unexpected token + - ``UnexpectedEOF``: The parser expected a token, but the input ended + + After catching one of these exceptions, you may call the following helper methods to create a nicer error message. + """ + line: int + column: int pos_in_stream = None + state: Any + _terminals_by_name = None - def get_context(self, text, span=40): + def get_context(self, text: str, span: int=40) -> str: + """Returns a pretty string pinpointing the error in the text, + with span amount of context characters around it. + + Note: + The parser doesn't hold a copy of the text it has to parse, + so you have to provide it again + """ + assert self.pos_in_stream is not None, self pos = self.pos_in_stream start = max(pos - span, 0) end = pos + span - before = text[start:pos].rsplit('\n', 1)[-1] - after = text[pos:end].split('\n', 1)[0] - return before + after + '\n' + ' ' * len(before) + '^\n' - - def match_examples(self, parse_fn, examples, token_type_match_fallback=False): - """ Given a parser instance and a dictionary mapping some label with - some malformed syntax examples, it'll return the label for the - example that bests matches the current error. + if not isinstance(text, bytes): + before = text[start:pos].rsplit('\n', 1)[-1] + after = text[pos:end].split('\n', 1)[0] + return before + after + '\n' + ' ' * len(before.expandtabs()) + '^\n' + else: + before = text[start:pos].rsplit(b'\n', 1)[-1] + after = text[pos:end].split(b'\n', 1)[0] + return (before + after + b'\n' + b' ' * len(before.expandtabs()) + b'^\n').decode("ascii", "backslashreplace") + + def match_examples(self, parse_fn: 'Callable[[str], Tree]', + examples: Union[Mapping[T, Iterable[str]], Iterable[Tuple[T, Iterable[str]]]], + token_type_match_fallback: bool=False, + use_accepts: bool=True + ) -> Optional[T]: + """Allows you to detect what's wrong in the input text by matching + against example errors. + + Given a parser instance and a dictionary mapping some label with + some malformed syntax examples, it'll return the label for the + example that bests matches the current error. The function will + iterate the dictionary until it finds a matching error, and + return the corresponding value. + + For an example usage, see `examples/error_reporting_lalr.py` + + Parameters: + parse_fn: parse function (usually ``lark_instance.parse``) + examples: dictionary of ``{'example_string': value}``. + use_accepts: Recommended to keep this as ``use_accepts=True``. """ assert self.state is not None, "Not supported for this exception" + if isinstance(examples, Mapping): + examples = examples.items() + candidate = (None, False) - for label, example in examples.items(): - assert not isinstance(example, STRING_TYPE) + for i, (label, example) in enumerate(examples): + assert not isinstance(example, str), "Expecting a list" - for malformed in example: + for j, malformed in enumerate(example): try: parse_fn(malformed) except UnexpectedInput as ut: if ut.state == self.state: - try: + if ( + use_accepts + and isinstance(self, UnexpectedToken) + and isinstance(ut, UnexpectedToken) + and ut.accepts != self.accepts + ): + logger.debug("Different accepts with same state[%d]: %s != %s at example [%s][%s]" % + (self.state, self.accepts, ut.accepts, i, j)) + continue + if ( + isinstance(self, (UnexpectedToken, UnexpectedEOF)) + and isinstance(ut, (UnexpectedToken, UnexpectedEOF)) + ): if ut.token == self.token: # Try exact match first + logger.debug("Exact Match at example [%s][%s]" % (i, j)) return label if token_type_match_fallback: # Fallback to token types match if (ut.token.type == self.token.type) and not candidate[-1]: + logger.debug("Token Type Fallback at example [%s][%s]" % (i, j)) candidate = label, True - except AttributeError: - pass - if not candidate[0]: + if candidate[0] is None: + logger.debug("Same State match at example [%s][%s]" % (i, j)) candidate = label, False return candidate[0] + def _format_expected(self, expected): + if self._terminals_by_name: + d = self._terminals_by_name + expected = [d[t_name].user_repr() if t_name in d else t_name for t_name in expected] + return "Expected one of: \n\t* %s\n" % '\n\t* '.join(expected) + + +class UnexpectedEOF(ParseError, UnexpectedInput): + """An exception that is raised by the parser, when the input ends while it still expects a token. + """ + expected: 'List[Token]' + + def __init__(self, expected, state=None, terminals_by_name=None): + super(UnexpectedEOF, self).__init__() + + self.expected = expected + self.state = state + from .lexer import Token + self.token = Token("", "") # , line=-1, column=-1, pos_in_stream=-1) + self.pos_in_stream = -1 + self.line = -1 + self.column = -1 + self._terminals_by_name = terminals_by_name + + + def __str__(self): + message = "Unexpected end-of-input. " + message += self._format_expected(self.expected) + return message + class UnexpectedCharacters(LexError, UnexpectedInput): - def __init__(self, seq, lex_pos, line, column, allowed=None, considered_tokens=None, state=None, token_history=None): - message = "No terminal defined for '%s' at line %d col %d" % (seq[lex_pos], line, column) + """An exception that is raised by the lexer, when it cannot match the next + string of characters to any of its terminals. + """ + + allowed: Set[str] + considered_tokens: Set[Any] + def __init__(self, seq, lex_pos, line, column, allowed=None, considered_tokens=None, state=None, token_history=None, + terminals_by_name=None, considered_rules=None): + super(UnexpectedCharacters, self).__init__() + + # TODO considered_tokens and allowed can be figured out using state self.line = line self.column = column - self.allowed = allowed - self.considered_tokens = considered_tokens self.pos_in_stream = lex_pos self.state = state + self._terminals_by_name = terminals_by_name - message += '\n\n' + self.get_context(seq) - if allowed: - message += '\nExpecting: %s\n' % allowed - if token_history: - message += '\nPrevious tokens: %s\n' % ', '.join(repr(t) for t in token_history) + self.allowed = allowed + self.considered_tokens = considered_tokens + self.considered_rules = considered_rules + self.token_history = token_history + + if isinstance(seq, bytes): + self.char = seq[lex_pos:lex_pos + 1].decode("ascii", "backslashreplace") + else: + self.char = seq[lex_pos] + self._context = self.get_context(seq) - super(UnexpectedCharacters, self).__init__(message) + def __str__(self): + message = "No terminal matches '%s' in the current parser context, at line %d col %d" % (self.char, self.line, self.column) + message += '\n\n' + self._context + if self.allowed: + message += self._format_expected(self.allowed) + if self.token_history: + message += '\nPrevious tokens: %s\n' % ', '.join(repr(t) for t in self.token_history) + return message class UnexpectedToken(ParseError, UnexpectedInput): - def __init__(self, token, expected, considered_rules=None, state=None, puppet=None): - self.token = token - self.expected = expected # XXX str shouldn't necessary + """An exception that is raised by the parser, when the token it received + doesn't match any valid step forward. + + Parameters: + token: The mismatched token + expected: The set of expected tokens + considered_rules: Which rules were considered, to deduce the expected tokens + state: A value representing the parser state. Do not rely on its value or type. + interactive_parser: An instance of ``InteractiveParser``, that is initialized to the point of failture, + and can be used for debugging and error handling. + + Note: These parameters are available as attributes of the instance. + """ + + expected: Set[str] + considered_rules: Set[str] + interactive_parser: 'InteractiveParser' + + def __init__(self, token, expected, considered_rules=None, state=None, interactive_parser=None, terminals_by_name=None, token_history=None): + super(UnexpectedToken, self).__init__() + + # TODO considered_rules and expected can be figured out using state self.line = getattr(token, 'line', '?') self.column = getattr(token, 'column', '?') - self.considered_rules = considered_rules + self.pos_in_stream = getattr(token, 'start_pos', None) self.state = state - self.pos_in_stream = getattr(token, 'pos_in_stream', None) - self.puppet = puppet - message = ("Unexpected token %r at line %s, column %s.\n" - "Expected one of: \n\t* %s\n" - % (token, self.line, self.column, '\n\t* '.join(self.expected))) + self.token = token + self.expected = expected # XXX deprecate? `accepts` is better + self._accepts = NO_VALUE + self.considered_rules = considered_rules + self.interactive_parser = interactive_parser + self._terminals_by_name = terminals_by_name + self.token_history = token_history + + + @property + def accepts(self) -> Set[str]: + if self._accepts is NO_VALUE: + self._accepts = self.interactive_parser and self.interactive_parser.accepts() + return self._accepts + + def __str__(self): + message = ("Unexpected token %r at line %s, column %s.\n%s" + % (self.token, self.line, self.column, self._format_expected(self.accepts or self.expected))) + if self.token_history: + message += "Previous tokens: %r\n" % self.token_history + + return message + - super(UnexpectedToken, self).__init__(message) class VisitError(LarkError): """VisitError is raised when visitors are interrupted by an exception It provides the following attributes for inspection: - - obj: the tree node or token it was processing when the exception was raised - - orig_exc: the exception that cause it to fail + + Parameters: + rule: the name of the visit rule that failed + obj: the tree-node or token that was being processed + orig_exc: the exception that cause it to fail + + Note: These parameters are available as attributes """ + + obj: 'Union[Tree, Token]' + orig_exc: Exception + def __init__(self, rule, obj, orig_exc): + message = 'Error trying to process rule "%s":\n\n%s' % (rule, orig_exc) + super(VisitError, self).__init__(message) + + self.rule = rule self.obj = obj self.orig_exc = orig_exc - message = 'Error trying to process rule "%s":\n\n%s' % (rule, orig_exc) - super(VisitError, self).__init__(message) + +class MissingVariableError(LarkError): + pass + ###} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/grammar.py b/conda_lock/_vendor/poetry/core/_vendor/lark/grammar.py index bb8435138..4f4fa90b5 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/grammar.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/grammar.py @@ -1,13 +1,18 @@ +from typing import Optional, Tuple, ClassVar + from .utils import Serialize ###{standalone +TOKEN_DEFAULT_PRIORITY = 0 + class Symbol(Serialize): __slots__ = ('name',) - is_term = NotImplemented + name: str + is_term: ClassVar[bool] = NotImplemented - def __init__(self, name): + def __init__(self, name: str) -> None: self.name = name def __eq__(self, other): @@ -25,11 +30,14 @@ def __repr__(self): fullrepr = property(__repr__) + def renamed(self, f): + return type(self)(f(self.name)) + class Terminal(Symbol): __serialize_fields__ = 'name', 'filter_out' - is_term = True + is_term: ClassVar[bool] = True def __init__(self, name, filter_out=False): self.name = name @@ -39,19 +47,26 @@ def __init__(self, name, filter_out=False): def fullrepr(self): return '%s(%r, %r)' % (type(self).__name__, self.name, self.filter_out) + def renamed(self, f): + return type(self)(f(self.name), self.filter_out) class NonTerminal(Symbol): __serialize_fields__ = 'name', - is_term = False - + is_term: ClassVar[bool] = False class RuleOptions(Serialize): __serialize_fields__ = 'keep_all_tokens', 'expand1', 'priority', 'template_source', 'empty_indices' - def __init__(self, keep_all_tokens=False, expand1=False, priority=None, template_source=None, empty_indices=()): + keep_all_tokens: bool + expand1: bool + priority: Optional[int] + template_source: Optional[str] + empty_indices: Tuple[bool, ...] + + def __init__(self, keep_all_tokens: bool=False, expand1: bool=False, priority: Optional[int]=None, template_source: Optional[str]=None, empty_indices: Tuple[bool, ...]=()) -> None: self.keep_all_tokens = keep_all_tokens self.expand1 = expand1 self.priority = priority @@ -104,5 +119,4 @@ def __eq__(self, other): return self.origin == other.origin and self.expansion == other.expansion - ###} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/common.lark b/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/common.lark index a675ca410..d2e86d17c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/common.lark +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/common.lark @@ -1,3 +1,6 @@ +// Basic terminals for common use + + // // Numbers // @@ -21,7 +24,7 @@ SIGNED_NUMBER: ["+"|"-"] NUMBER // Strings // _STRING_INNER: /.*?/ -_STRING_ESC_INNER: _STRING_INNER /(? ignore + | "%import" import_path ["->" name] -> import + | "%import" import_path name_list -> multi_import + | "%override" rule -> override_rule + | "%declare" name+ -> declare + +!import_path: "."? name ("." name)* +name_list: "(" name ("," name)* ")" + +?expansions: alias (_VBAR alias)* + +?alias: expansion ["->" RULE] + +?expansion: expr* + +?expr: atom [OP | "~" NUMBER [".." NUMBER]] + +?atom: "(" expansions ")" + | "[" expansions "]" -> maybe + | value + +?value: STRING ".." STRING -> literal_range + | name + | (REGEXP | STRING) -> literal + | name "{" value ("," value)* "}" -> template_usage + +name: RULE + | TOKEN + +_VBAR: _NL? "|" +OP: /[+*]|[?](?![a-z])/ +RULE: /!?[_?]?[a-z][_a-z0-9]*/ +TOKEN: /_?[A-Z][_A-Z0-9]*/ +STRING: _STRING "i"? +REGEXP: /\/(?!\/)(\\\/|\\\\|[^\/])*?\/[imslux]*/ +_NL: /(\r?\n)+\s*/ + +%import common.ESCAPED_STRING -> _STRING +%import common.SIGNED_INT -> NUMBER +%import common.WS_INLINE + +COMMENT: /\s*/ "//" /[^\n]/* + +%ignore WS_INLINE +%ignore COMMENT diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/python.lark b/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/python.lark new file mode 100644 index 000000000..ab4a26a49 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/grammars/python.lark @@ -0,0 +1,304 @@ +// Python 3 grammar for Lark + +// This grammar should parse all python 3.x code successfully. + +// Adapted from: https://docs.python.org/3/reference/grammar.html + +// Start symbols for the grammar: +// single_input is a single interactive statement; +// file_input is a module or sequence of commands read from an input file; +// eval_input is the input for the eval() functions. +// NB: compound_stmt in single_input is followed by extra NEWLINE! +// + +single_input: _NEWLINE | simple_stmt | compound_stmt _NEWLINE +file_input: (_NEWLINE | stmt)* +eval_input: testlist _NEWLINE* + +decorator: "@" dotted_name [ "(" [arguments] ")" ] _NEWLINE +decorators: decorator+ +decorated: decorators (classdef | funcdef | async_funcdef) + +async_funcdef: "async" funcdef +funcdef: "def" name "(" [parameters] ")" ["->" test] ":" suite + +parameters: paramvalue ("," paramvalue)* ["," SLASH ("," paramvalue)*] ["," [starparams | kwparams]] + | starparams + | kwparams + +SLASH: "/" // Otherwise the it will completely disappear and it will be undisguisable in the result +starparams: (starparam | starguard) poststarparams +starparam: "*" typedparam +starguard: "*" +poststarparams: ("," paramvalue)* ["," kwparams] +kwparams: "**" typedparam ","? + +?paramvalue: typedparam ("=" test)? +?typedparam: name (":" test)? + + +lambdef: "lambda" [lambda_params] ":" test +lambdef_nocond: "lambda" [lambda_params] ":" test_nocond +lambda_params: lambda_paramvalue ("," lambda_paramvalue)* ["," [lambda_starparams | lambda_kwparams]] + | lambda_starparams + | lambda_kwparams +?lambda_paramvalue: name ("=" test)? +lambda_starparams: "*" [name] ("," lambda_paramvalue)* ["," [lambda_kwparams]] +lambda_kwparams: "**" name ","? + + +?stmt: simple_stmt | compound_stmt +?simple_stmt: small_stmt (";" small_stmt)* [";"] _NEWLINE +?small_stmt: (expr_stmt | assign_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | nonlocal_stmt | assert_stmt) +expr_stmt: testlist_star_expr +assign_stmt: annassign | augassign | assign + +annassign: testlist_star_expr ":" test ["=" test] +assign: testlist_star_expr ("=" (yield_expr|testlist_star_expr))+ +augassign: testlist_star_expr augassign_op (yield_expr|testlist) +!augassign_op: "+=" | "-=" | "*=" | "@=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" | "**=" | "//=" +?testlist_star_expr: test_or_star_expr + | test_or_star_expr ("," test_or_star_expr)+ ","? -> tuple + | test_or_star_expr "," -> tuple + +// For normal and annotated assignments, additional restrictions enforced by the interpreter +del_stmt: "del" exprlist +pass_stmt: "pass" +?flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt +break_stmt: "break" +continue_stmt: "continue" +return_stmt: "return" [testlist] +yield_stmt: yield_expr +raise_stmt: "raise" [test ["from" test]] +import_stmt: import_name | import_from +import_name: "import" dotted_as_names +// note below: the ("." | "...") is necessary because "..." is tokenized as ELLIPSIS +import_from: "from" (dots? dotted_name | dots) "import" ("*" | "(" import_as_names ")" | import_as_names) +!dots: "."+ +import_as_name: name ["as" name] +dotted_as_name: dotted_name ["as" name] +import_as_names: import_as_name ("," import_as_name)* [","] +dotted_as_names: dotted_as_name ("," dotted_as_name)* +dotted_name: name ("." name)* +global_stmt: "global" name ("," name)* +nonlocal_stmt: "nonlocal" name ("," name)* +assert_stmt: "assert" test ["," test] + +?compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | match_stmt + | with_stmt | funcdef | classdef | decorated | async_stmt +async_stmt: "async" (funcdef | with_stmt | for_stmt) +if_stmt: "if" test ":" suite elifs ["else" ":" suite] +elifs: elif_* +elif_: "elif" test ":" suite +while_stmt: "while" test ":" suite ["else" ":" suite] +for_stmt: "for" exprlist "in" testlist ":" suite ["else" ":" suite] +try_stmt: "try" ":" suite except_clauses ["else" ":" suite] [finally] + | "try" ":" suite finally -> try_finally +finally: "finally" ":" suite +except_clauses: except_clause+ +except_clause: "except" [test ["as" name]] ":" suite +// NB compile.c makes sure that the default except clause is last + + +with_stmt: "with" with_items ":" suite +with_items: with_item ("," with_item)* +with_item: test ["as" name] + +match_stmt: "match" test ":" _NEWLINE _INDENT case+ _DEDENT + +case: "case" pattern ["if" test] ":" suite + +?pattern: sequence_item_pattern "," _sequence_pattern -> sequence_pattern + | as_pattern +?as_pattern: or_pattern ("as" NAME)? +?or_pattern: closed_pattern ("|" closed_pattern)* +?closed_pattern: literal_pattern + | NAME -> capture_pattern + | "_" -> any_pattern + | attr_pattern + | "(" as_pattern ")" + | "[" _sequence_pattern "]" -> sequence_pattern + | "(" (sequence_item_pattern "," _sequence_pattern)? ")" -> sequence_pattern + | "{" (mapping_item_pattern ("," mapping_item_pattern)* ","?)?"}" -> mapping_pattern + | "{" (mapping_item_pattern ("," mapping_item_pattern)* ",")? "**" NAME ","? "}" -> mapping_star_pattern + | class_pattern + +literal_pattern: inner_literal_pattern + +?inner_literal_pattern: "None" -> const_none + | "True" -> const_true + | "False" -> const_false + | STRING -> string + | number + +attr_pattern: NAME ("." NAME)+ -> value + +name_or_attr_pattern: NAME ("." NAME)* -> value + +mapping_item_pattern: (literal_pattern|attr_pattern) ":" as_pattern + +_sequence_pattern: (sequence_item_pattern ("," sequence_item_pattern)* ","?)? +?sequence_item_pattern: as_pattern + | "*" NAME -> star_pattern + +class_pattern: name_or_attr_pattern "(" [arguments_pattern ","?] ")" +arguments_pattern: pos_arg_pattern ["," keyws_arg_pattern] + | keyws_arg_pattern -> no_pos_arguments + +pos_arg_pattern: as_pattern ("," as_pattern)* +keyws_arg_pattern: keyw_arg_pattern ("," keyw_arg_pattern)* +keyw_arg_pattern: NAME "=" as_pattern + + + +suite: simple_stmt | _NEWLINE _INDENT stmt+ _DEDENT + +?test: or_test ("if" or_test "else" test)? + | lambdef + | assign_expr + +assign_expr: name ":=" test + +?test_nocond: or_test | lambdef_nocond + +?or_test: and_test ("or" and_test)* +?and_test: not_test_ ("and" not_test_)* +?not_test_: "not" not_test_ -> not_test + | comparison +?comparison: expr (comp_op expr)* +star_expr: "*" expr + +?expr: or_expr +?or_expr: xor_expr ("|" xor_expr)* +?xor_expr: and_expr ("^" and_expr)* +?and_expr: shift_expr ("&" shift_expr)* +?shift_expr: arith_expr (_shift_op arith_expr)* +?arith_expr: term (_add_op term)* +?term: factor (_mul_op factor)* +?factor: _unary_op factor | power + +!_unary_op: "+"|"-"|"~" +!_add_op: "+"|"-" +!_shift_op: "<<"|">>" +!_mul_op: "*"|"@"|"/"|"%"|"//" +// <> isn't actually a valid comparison operator in Python. It's here for the +// sake of a __future__ import described in PEP 401 (which really works :-) +!comp_op: "<"|">"|"=="|">="|"<="|"<>"|"!="|"in"|"not" "in"|"is"|"is" "not" + +?power: await_expr ("**" factor)? +?await_expr: AWAIT? atom_expr +AWAIT: "await" + +?atom_expr: atom_expr "(" [arguments] ")" -> funccall + | atom_expr "[" subscriptlist "]" -> getitem + | atom_expr "." name -> getattr + | atom + +?atom: "(" yield_expr ")" + | "(" _tuple_inner? ")" -> tuple + | "(" comprehension{test_or_star_expr} ")" -> tuple_comprehension + | "[" _testlist_comp? "]" -> list + | "[" comprehension{test_or_star_expr} "]" -> list_comprehension + | "{" _dict_exprlist? "}" -> dict + | "{" comprehension{key_value} "}" -> dict_comprehension + | "{" _set_exprlist "}" -> set + | "{" comprehension{test} "}" -> set_comprehension + | name -> var + | number + | string_concat + | "(" test ")" + | "..." -> ellipsis + | "None" -> const_none + | "True" -> const_true + | "False" -> const_false + + +?string_concat: string+ + +_testlist_comp: test | _tuple_inner +_tuple_inner: test_or_star_expr (("," test_or_star_expr)+ [","] | ",") + + +?test_or_star_expr: test + | star_expr + +?subscriptlist: subscript + | subscript (("," subscript)+ [","] | ",") -> subscript_tuple +?subscript: test | ([test] ":" [test] [sliceop]) -> slice +sliceop: ":" [test] +?exprlist: (expr|star_expr) + | (expr|star_expr) (("," (expr|star_expr))+ [","]|",") +?testlist: test | testlist_tuple +testlist_tuple: test (("," test)+ [","] | ",") +_dict_exprlist: (key_value | "**" expr) ("," (key_value | "**" expr))* [","] + +key_value: test ":" test + +_set_exprlist: test_or_star_expr ("," test_or_star_expr)* [","] + +classdef: "class" name ["(" [arguments] ")"] ":" suite + + + +arguments: argvalue ("," argvalue)* ("," [ starargs | kwargs])? + | starargs + | kwargs + | comprehension{test} + +starargs: stararg ("," stararg)* ("," argvalue)* ["," kwargs] +stararg: "*" test +kwargs: "**" test ("," argvalue)* + +?argvalue: test ("=" test)? + + +comprehension{comp_result}: comp_result comp_fors [comp_if] +comp_fors: comp_for+ +comp_for: [ASYNC] "for" exprlist "in" or_test +ASYNC: "async" +?comp_if: "if" test_nocond + +// not used in grammar, but may appear in "node" passed from Parser to Compiler +encoding_decl: name + +yield_expr: "yield" [testlist] + | "yield" "from" test -> yield_from + +number: DEC_NUMBER | HEX_NUMBER | BIN_NUMBER | OCT_NUMBER | FLOAT_NUMBER | IMAG_NUMBER +string: STRING | LONG_STRING + +// Other terminals + +_NEWLINE: ( /\r?\n[\t ]*/ | COMMENT )+ + +%ignore /[\t \f]+/ // WS +%ignore /\\[\t \f]*\r?\n/ // LINE_CONT +%ignore COMMENT +%declare _INDENT _DEDENT + + +// Python terminals + +!name: NAME | "match" | "case" +NAME: /[^\W\d]\w*/ +COMMENT: /#[^\n]*/ + +STRING: /([ubf]?r?|r[ubf])("(?!"").*?(? None: + self.paren_level = 0 + self.indent_level = [0] assert self.tab_len > 0 - def handle_NL(self, token): + def handle_NL(self, token: Token) -> Iterator[Token]: if self.paren_level > 0: return @@ -26,13 +38,13 @@ def handle_NL(self, token): self.indent_level.pop() yield Token.new_borrow_pos(self.DEDENT_type, indent_str, token) - assert indent == self.indent_level[-1], '%s != %s' % (indent, self.indent_level[-1]) + if indent != self.indent_level[-1]: + raise DedentError('Unexpected dedent to column %s. Expected dedent to %s' % (indent, self.indent_level[-1])) def _process(self, stream): for token in stream: if token.type == self.NL_type: - for t in self.handle_NL(token): - yield t + yield from self.handle_NL(token) else: yield token @@ -58,4 +70,43 @@ def process(self, stream): def always_accept(self): return (self.NL_type,) + @property + @abstractmethod + def NL_type(self) -> str: + raise NotImplementedError() + + @property + @abstractmethod + def OPEN_PAREN_types(self) -> List[str]: + raise NotImplementedError() + + @property + @abstractmethod + def CLOSE_PAREN_types(self) -> List[str]: + raise NotImplementedError() + + @property + @abstractmethod + def INDENT_type(self) -> str: + raise NotImplementedError() + + @property + @abstractmethod + def DEDENT_type(self) -> str: + raise NotImplementedError() + + @property + @abstractmethod + def tab_len(self) -> int: + raise NotImplementedError() + + +class PythonIndenter(Indenter): + NL_type = '_NEWLINE' + OPEN_PAREN_types = ['LPAR', 'LSQB', 'LBRACE'] + CLOSE_PAREN_types = ['RPAR', 'RSQB', 'RBRACE'] + INDENT_type = '_INDENT' + DEDENT_type = '_DEDENT' + tab_len = 8 + ###} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/lark.py b/conda_lock/_vendor/poetry/core/_vendor/lark/lark.py index 2b783cb20..c93e9e19c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/lark.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/lark.py @@ -1,93 +1,160 @@ -from __future__ import absolute_import - -import sys, os, pickle, hashlib, logging -from io import open - - -from .utils import STRING_TYPE, Serialize, SerializeMemoizer, FS -from .load_grammar import load_grammar +from abc import ABC, abstractmethod +import getpass +import sys, os, pickle +import tempfile +import types +import re +from typing import ( + TypeVar, Type, List, Dict, Iterator, Callable, Union, Optional, Sequence, + Tuple, Iterable, IO, Any, TYPE_CHECKING, Collection +) +if TYPE_CHECKING: + from .parsers.lalr_interactive_parser import InteractiveParser + from .tree import ParseTree + from .visitors import Transformer + if sys.version_info >= (3, 8): + from typing import Literal + else: + from typing_extensions import Literal + from .parser_frontends import ParsingFrontend + +from .exceptions import ConfigurationError, assert_config, UnexpectedInput +from .utils import Serialize, SerializeMemoizer, FS, isascii, logger +from .load_grammar import load_grammar, FromPackageLoader, Grammar, verify_used_files, PackageResource, md5_digest from .tree import Tree -from .common import LexerConf, ParserConf +from .common import LexerConf, ParserConf, _ParserArgType, _LexerArgType -from .lexer import Lexer, TraditionalLexer, TerminalDef, UnexpectedToken +from .lexer import Lexer, BasicLexer, TerminalDef, LexerThread, Token from .parse_tree_builder import ParseTreeBuilder -from .parser_frontends import get_frontend +from .parser_frontends import _validate_frontend_args, _get_lexer_callbacks, _deserialize_parsing_frontend, _construct_parsing_frontend from .grammar import Rule -import re + try: import regex + _has_regex = True except ImportError: - regex = None + _has_regex = False + ###{standalone + +class PostLex(ABC): + @abstractmethod + def process(self, stream: Iterator[Token]) -> Iterator[Token]: + return stream + + always_accept: Iterable[str] = () + class LarkOptions(Serialize): """Specifies the options for Lark """ + + start: List[str] + debug: bool + transformer: 'Optional[Transformer]' + propagate_positions: Union[bool, str] + maybe_placeholders: bool + cache: Union[bool, str] + regex: bool + g_regex_flags: int + keep_all_tokens: bool + tree_class: Any + parser: _ParserArgType + lexer: _LexerArgType + ambiguity: 'Literal["auto", "resolve", "explicit", "forest"]' + postlex: Optional[PostLex] + priority: 'Optional[Literal["auto", "normal", "invert"]]' + lexer_callbacks: Dict[str, Callable[[Token], Token]] + use_bytes: bool + edit_terminals: Optional[Callable[[TerminalDef], TerminalDef]] + import_paths: 'List[Union[str, Callable[[Union[None, str, PackageResource], str], Tuple[str, str]]]]' + source_path: Optional[str] + OPTIONS_DOC = """ -# General - - start - The start symbol. Either a string, or a list of strings for - multiple possible starts (Default: "start") - debug - Display debug information, such as warnings (default: False) - transformer - Applies the transformer to every parse tree (equivlent to - applying it after the parse, but faster) - propagate_positions - Propagates (line, column, end_line, end_column) - attributes into all tree branches. - maybe_placeholders - When True, the `[]` operator returns `None` when not matched. - When `False`, `[]` behaves like the `?` operator, - and returns no value at all. - (default=`False`. Recommended to set to `True`) - regex - When True, uses the `regex` module instead of the stdlib `re`. - cache - Cache the results of the Lark grammar analysis, for x2 to x3 faster loading. - LALR only for now. - When `False`, does nothing (default) - When `True`, caches to a temporary file in the local directory - When given a string, caches to the path pointed by the string - - g_regex_flags - Flags that are applied to all terminals - (both regex and strings) - keep_all_tokens - Prevent the tree builder from automagically - removing "punctuation" tokens (default: False) - -# Algorithm - - parser - Decides which parser engine to use - Accepts "earley" or "lalr". (Default: "earley") - (there is also a "cyk" option for legacy) - - lexer - Decides whether or not to use a lexer stage - "auto" (default): Choose for me based on the parser - "standard": Use a standard lexer - "contextual": Stronger lexer (only works with parser="lalr") - "dynamic": Flexible and powerful (only with parser="earley") - "dynamic_complete": Same as dynamic, but tries *every* variation - of tokenizing possible. - - ambiguity - Decides how to handle ambiguity in the parse. - Only relevant if parser="earley" - "resolve": The parser will automatically choose the simplest - derivation (it chooses consistently: greedy for - tokens, non-greedy for rules) - "explicit": The parser will return all derivations wrapped - in "_ambig" tree nodes (i.e. a forest). - -# Domain Specific - - postlex - Lexer post-processing (Default: None) Only works with the - standard and contextual lexers. - priority - How priorities should be evaluated - auto, none, normal, - invert (Default: auto) - lexer_callbacks - Dictionary of callbacks for the lexer. May alter - tokens during lexing. Use with caution. - edit_terminals - A callback + **=== General Options ===** + + start + The start symbol. Either a string, or a list of strings for multiple possible starts (Default: "start") + debug + Display debug information and extra warnings. Use only when debugging (Default: ``False``) + When used with Earley, it generates a forest graph as "sppf.png", if 'dot' is installed. + transformer + Applies the transformer to every parse tree (equivalent to applying it after the parse, but faster) + propagate_positions + Propagates (line, column, end_line, end_column) attributes into all tree branches. + Accepts ``False``, ``True``, or a callable, which will filter which nodes to ignore when propagating. + maybe_placeholders + When ``True``, the ``[]`` operator returns ``None`` when not matched. + When ``False``, ``[]`` behaves like the ``?`` operator, and returns no value at all. + (default= ``True``) + cache + Cache the results of the Lark grammar analysis, for x2 to x3 faster loading. LALR only for now. + + - When ``False``, does nothing (default) + - When ``True``, caches to a temporary file in the local directory + - When given a string, caches to the path pointed by the string + regex + When True, uses the ``regex`` module instead of the stdlib ``re``. + g_regex_flags + Flags that are applied to all terminals (both regex and strings) + keep_all_tokens + Prevent the tree builder from automagically removing "punctuation" tokens (Default: ``False``) + tree_class + Lark will produce trees comprised of instances of this class instead of the default ``lark.Tree``. + + **=== Algorithm Options ===** + + parser + Decides which parser engine to use. Accepts "earley" or "lalr". (Default: "earley"). + (there is also a "cyk" option for legacy) + lexer + Decides whether or not to use a lexer stage + + - "auto" (default): Choose for me based on the parser + - "basic": Use a basic lexer + - "contextual": Stronger lexer (only works with parser="lalr") + - "dynamic": Flexible and powerful (only with parser="earley") + - "dynamic_complete": Same as dynamic, but tries *every* variation of tokenizing possible. + ambiguity + Decides how to handle ambiguity in the parse. Only relevant if parser="earley" + + - "resolve": The parser will automatically choose the simplest derivation + (it chooses consistently: greedy for tokens, non-greedy for rules) + - "explicit": The parser will return all derivations wrapped in "_ambig" tree nodes (i.e. a forest). + - "forest": The parser will return the root of the shared packed parse forest. + + **=== Misc. / Domain Specific Options ===** + + postlex + Lexer post-processing (Default: ``None``) Only works with the basic and contextual lexers. + priority + How priorities should be evaluated - "auto", ``None``, "normal", "invert" (Default: "auto") + lexer_callbacks + Dictionary of callbacks for the lexer. May alter tokens during lexing. Use with caution. + use_bytes + Accept an input of type ``bytes`` instead of ``str``. + edit_terminals + A callback for editing the terminals before parse. + import_paths + A List of either paths or loader functions to specify from where grammars are imported + source_path + Override the source of from where the grammar was loaded. Useful for relative imports and unconventional grammar loading + **=== End of Options ===** """ if __doc__: __doc__ += OPTIONS_DOC - _defaults = { + + # Adding a new option needs to be done in multiple places: + # - In the dictionary below. This is the primary truth of which options `Lark.__init__` accepts + # - In the docstring above. It is used both for the docstring of `LarkOptions` and `Lark`, and in readthedocs + # - As an attribute of `LarkOptions` above + # - Potentially in `_LOAD_ALLOWED_OPTIONS` below this class, when the option doesn't change how the grammar is loaded + # - Potentially in `lark.tools.__init__`, if it makes sense, and it can easily be passed as a cmd argument + _defaults: Dict[str, Any] = { 'debug': False, 'keep_all_tokens': False, 'tree_class': None, @@ -102,181 +169,263 @@ class LarkOptions(Serialize): 'regex': False, 'propagate_positions': False, 'lexer_callbacks': {}, - 'maybe_placeholders': False, + 'maybe_placeholders': True, 'edit_terminals': None, 'g_regex_flags': 0, + 'use_bytes': False, + 'import_paths': [], + 'source_path': None, + '_plugins': {}, } - def __init__(self, options_dict): + def __init__(self, options_dict: Dict[str, Any]) -> None: o = dict(options_dict) options = {} for name, default in self._defaults.items(): if name in o: value = o.pop(name) - if isinstance(default, bool) and name != 'cache': + if isinstance(default, bool) and name not in ('cache', 'use_bytes', 'propagate_positions'): value = bool(value) else: value = default options[name] = value - if isinstance(options['start'], STRING_TYPE): + if isinstance(options['start'], str): options['start'] = [options['start']] self.__dict__['options'] = options - assert self.parser in ('earley', 'lalr', 'cyk', None) + + assert_config(self.parser, ('earley', 'lalr', 'cyk', None)) if self.parser == 'earley' and self.transformer: - raise ValueError('Cannot specify an embedded transformer when using the Earley algorithm.' + raise ConfigurationError('Cannot specify an embedded transformer when using the Earley algorithm. ' 'Please use your transformer on the resulting parse tree, or use a different algorithm (i.e. LALR)') if o: - raise ValueError("Unknown options: %s" % o.keys()) + raise ConfigurationError("Unknown options: %s" % o.keys()) - def __getattr__(self, name): + def __getattr__(self, name: str) -> Any: try: - return self.options[name] + return self.__dict__['options'][name] except KeyError as e: raise AttributeError(e) - def __setattr__(self, name, value): - assert name in self.options + def __setattr__(self, name: str, value: str) -> None: + assert_config(name, self.options.keys(), "%r isn't a valid option. Expected one of: %s") self.options[name] = value - def serialize(self, memo): + def serialize(self, memo = None) -> Dict[str, Any]: return self.options @classmethod - def deserialize(cls, data, memo): + def deserialize(cls, data: Dict[str, Any], memo: Dict[int, Union[TerminalDef, Rule]]) -> "LarkOptions": return cls(data) +# Options that can be passed to the Lark parser, even when it was loaded from cache/standalone. +# These options are only used outside of `load_grammar`. +_LOAD_ALLOWED_OPTIONS = {'postlex', 'transformer', 'lexer_callbacks', 'use_bytes', 'debug', 'g_regex_flags', 'regex', 'propagate_positions', 'tree_class', '_plugins'} + +_VALID_PRIORITY_OPTIONS = ('auto', 'normal', 'invert', None) +_VALID_AMBIGUITY_OPTIONS = ('auto', 'resolve', 'explicit', 'forest') + + +_T = TypeVar('_T', bound="Lark") + class Lark(Serialize): - def __init__(self, grammar, **options): - """ - grammar : a string or file-object containing the grammar spec (using Lark's ebnf syntax) - options : a dictionary controlling various aspects of Lark. - """ + """Main interface for the library. + + It's mostly a thin wrapper for the many different parsers, and for the tree constructor. + + Parameters: + grammar: a string or file-object containing the grammar spec (using Lark's ebnf syntax) + options: a dictionary controlling various aspects of Lark. + Example: + >>> Lark(r'''start: "foo" ''') + Lark(...) + """ + + source_path: str + source_grammar: str + grammar: 'Grammar' + options: LarkOptions + lexer: Lexer + terminals: Collection[TerminalDef] + + def __init__(self, grammar: 'Union[Grammar, str, IO[str]]', **options) -> None: self.options = LarkOptions(options) + re_module: types.ModuleType # Set regex or re module use_regex = self.options.regex if use_regex: - if regex: - self.re = regex + if _has_regex: + re_module = regex else: raise ImportError('`regex` module must be installed if calling `Lark(regex=True)`.') else: - self.re = re + re_module = re # Some, but not all file-like objects have a 'name' attribute - try: - self.source = grammar.name - except AttributeError: - self.source = '' + if self.options.source_path is None: + try: + self.source_path = grammar.name # type: ignore[union-attr] + except AttributeError: + self.source_path = '' + else: + self.source_path = self.options.source_path # Drain file-like objects to get their contents try: - read = grammar.read + read = grammar.read # type: ignore[union-attr] except AttributeError: pass else: grammar = read() - assert isinstance(grammar, STRING_TYPE) - cache_fn = None - if self.options.cache: - if self.options.parser != 'lalr': - raise NotImplementedError("cache only works with parser='lalr' for now") - if isinstance(self.options.cache, STRING_TYPE): - cache_fn = self.options.cache - else: - if self.options.cache is not True: - raise ValueError("cache must be bool or str") - unhashable = ('transformer', 'postlex', 'lexer_callbacks', 'edit_terminals') - from . import __version__ + cache_md5 = None + if isinstance(grammar, str): + self.source_grammar = grammar + if self.options.use_bytes: + if not isascii(grammar): + raise ConfigurationError("Grammar must be ascii only, when use_bytes=True") + + if self.options.cache: + if self.options.parser != 'lalr': + raise ConfigurationError("cache only works with parser='lalr' for now") + + unhashable = ('transformer', 'postlex', 'lexer_callbacks', 'edit_terminals', '_plugins') options_str = ''.join(k+str(v) for k, v in options.items() if k not in unhashable) - s = grammar + options_str + __version__ - md5 = hashlib.md5(s.encode()).hexdigest() - cache_fn = '.lark_cache_%s.tmp' % md5 + from . import __version__ + s = grammar + options_str + __version__ + str(sys.version_info[:2]) + cache_md5 = md5_digest(s) + + if isinstance(self.options.cache, str): + cache_fn = self.options.cache + else: + if self.options.cache is not True: + raise ConfigurationError("cache argument must be bool or str") + + try: + username = getpass.getuser() + except Exception: + # The exception raised may be ImportError or OSError in + # the future. For the cache, we don't care about the + # specific reason - we just want a username. + username = "unknown" + + cache_fn = tempfile.gettempdir() + "/.lark_cache_%s_%s_%s_%s.tmp" % (username, cache_md5, *sys.version_info[:2]) + + old_options = self.options + try: + with FS.open(cache_fn, 'rb') as f: + logger.debug('Loading grammar from cache: %s', cache_fn) + # Remove options that aren't relevant for loading from cache + for name in (set(options) - _LOAD_ALLOWED_OPTIONS): + del options[name] + file_md5 = f.readline().rstrip(b'\n') + cached_used_files = pickle.load(f) + if file_md5 == cache_md5.encode('utf8') and verify_used_files(cached_used_files): + cached_parser_data = pickle.load(f) + self._load(cached_parser_data, **options) + return + except FileNotFoundError: + # The cache file doesn't exist; parse and compose the grammar as normal + pass + except Exception: # We should probably narrow done which errors we catch here. + logger.exception("Failed to load Lark from cache: %r. We will try to carry on.", cache_fn) + + # In theory, the Lark instance might have been messed up by the call to `_load`. + # In practice the only relevant thing that might have been overwritten should be `options` + self.options = old_options + + + # Parse the grammar file and compose the grammars + self.grammar, used_files = load_grammar(grammar, self.source_path, self.options.import_paths, self.options.keep_all_tokens) + else: + assert isinstance(grammar, Grammar) + self.grammar = grammar - if FS.exists(cache_fn): - logging.debug('Loading grammar from cache: %s', cache_fn) - with FS.open(cache_fn, 'rb') as f: - self._load(f, self.options.transformer, self.options.postlex) - return if self.options.lexer == 'auto': if self.options.parser == 'lalr': self.options.lexer = 'contextual' elif self.options.parser == 'earley': - self.options.lexer = 'dynamic' + if self.options.postlex is not None: + logger.info("postlex can't be used with the dynamic lexer, so we use 'basic' instead. " + "Consider using lalr with contextual instead of earley") + self.options.lexer = 'basic' + else: + self.options.lexer = 'dynamic' elif self.options.parser == 'cyk': - self.options.lexer = 'standard' + self.options.lexer = 'basic' else: assert False, self.options.parser lexer = self.options.lexer - assert lexer in ('standard', 'contextual', 'dynamic', 'dynamic_complete') or issubclass(lexer, Lexer) + if isinstance(lexer, type): + assert issubclass(lexer, Lexer) # XXX Is this really important? Maybe just ensure interface compliance + else: + assert_config(lexer, ('basic', 'contextual', 'dynamic', 'dynamic_complete')) + if self.options.postlex is not None and 'dynamic' in lexer: + raise ConfigurationError("Can't use postlex with a dynamic lexer. Use basic or contextual instead") if self.options.ambiguity == 'auto': if self.options.parser == 'earley': self.options.ambiguity = 'resolve' else: - disambig_parsers = ['earley', 'cyk'] - assert self.options.parser in disambig_parsers, ( - 'Only %s supports disambiguation right now') % ', '.join(disambig_parsers) + assert_config(self.options.parser, ('earley', 'cyk'), "%r doesn't support disambiguation. Use one of these parsers instead: %s") if self.options.priority == 'auto': - if self.options.parser in ('earley', 'cyk', ): - self.options.priority = 'normal' - elif self.options.parser in ('lalr', ): - self.options.priority = None - elif self.options.priority in ('invert', 'normal'): - assert self.options.parser in ('earley', 'cyk'), "priorities are not supported for LALR at this time" + self.options.priority = 'normal' - assert self.options.priority in ('auto', None, 'normal', 'invert'), 'invalid priority option specified: {}. options are auto, none, normal, invert.'.format(self.options.priority) - assert self.options.ambiguity not in ('resolve__antiscore_sum', ), 'resolve__antiscore_sum has been replaced with the option priority="invert"' - assert self.options.ambiguity in ('resolve', 'explicit', 'auto', ) + if self.options.priority not in _VALID_PRIORITY_OPTIONS: + raise ConfigurationError("invalid priority option: %r. Must be one of %r" % (self.options.priority, _VALID_PRIORITY_OPTIONS)) + if self.options.ambiguity not in _VALID_AMBIGUITY_OPTIONS: + raise ConfigurationError("invalid ambiguity option: %r. Must be one of %r" % (self.options.ambiguity, _VALID_AMBIGUITY_OPTIONS)) - # Parse the grammar file and compose the grammars (TODO) - self.grammar = load_grammar(grammar, self.source, self.re) + if self.options.parser is None: + terminals_to_keep = '*' + elif self.options.postlex is not None: + terminals_to_keep = set(self.options.postlex.always_accept) + else: + terminals_to_keep = set() # Compile the EBNF grammar into BNF - self.terminals, self.rules, self.ignore_tokens = self.grammar.compile(self.options.start) + self.terminals, self.rules, self.ignore_tokens = self.grammar.compile(self.options.start, terminals_to_keep) if self.options.edit_terminals: for t in self.terminals: self.options.edit_terminals(t) - self._terminals_dict = {t.name:t for t in self.terminals} + self._terminals_dict = {t.name: t for t in self.terminals} # If the user asked to invert the priorities, negate them all here. - # This replaces the old 'resolve__antiscore_sum' option. if self.options.priority == 'invert': for rule in self.rules: if rule.options.priority is not None: rule.options.priority = -rule.options.priority + for term in self.terminals: + term.priority = -term.priority # Else, if the user asked to disable priorities, strip them from the - # rules. This allows the Earley parsers to skip an extra forest walk + # rules and terminals. This allows the Earley parsers to skip an extra forest walk # for improved performance, if you don't need them (or didn't specify any). - elif self.options.priority == None: + elif self.options.priority is None: for rule in self.rules: if rule.options.priority is not None: rule.options.priority = None - - # TODO Deprecate lexer_callbacks? - lexer_callbacks = dict(self.options.lexer_callbacks) - if self.options.transformer: - t = self.options.transformer for term in self.terminals: - if hasattr(t, term.name): - lexer_callbacks[term.name] = getattr(t, term.name) + term.priority = 0 - self.lexer_conf = LexerConf(self.terminals, self.ignore_tokens, self.options.postlex, lexer_callbacks, self.options.g_regex_flags) + # TODO Deprecate lexer_callbacks? + self.lexer_conf = LexerConf( + self.terminals, re_module, self.ignore_tokens, self.options.postlex, + self.options.lexer_callbacks, self.options.g_regex_flags, use_bytes=self.options.use_bytes + ) if self.options.parser: self.parser = self._build_parser() @@ -284,70 +433,126 @@ def __init__(self, grammar, **options): self.lexer = self._build_lexer() if cache_fn: - logging.debug('Saving grammar to cache: %s', cache_fn) - with FS.open(cache_fn, 'wb') as f: - self.save(f) + logger.debug('Saving grammar to cache: %s', cache_fn) + try: + with FS.open(cache_fn, 'wb') as f: + assert cache_md5 is not None + f.write(cache_md5.encode('utf8') + b'\n') + pickle.dump(used_files, f) + self.save(f, _LOAD_ALLOWED_OPTIONS) + except IOError as e: + logger.exception("Failed to save Lark to cache: %r.", cache_fn, e) - if __init__.__doc__: - __init__.__doc__ += "\nOptions:\n" + LarkOptions.OPTIONS_DOC + if __doc__: + __doc__ += "\n\n" + LarkOptions.OPTIONS_DOC __serialize_fields__ = 'parser', 'rules', 'options' - def _build_lexer(self): - return TraditionalLexer(self.lexer_conf.tokens, ignore=self.lexer_conf.ignore, user_callbacks=self.lexer_conf.callbacks, g_regex_flags=self.lexer_conf.g_regex_flags) - - def _prepare_callbacks(self): - self.parser_class = get_frontend(self.options.parser, self.options.lexer) - self._parse_tree_builder = ParseTreeBuilder(self.rules, self.options.tree_class or Tree, self.options.propagate_positions, self.options.keep_all_tokens, self.options.parser!='lalr' and self.options.ambiguity=='explicit', self.options.maybe_placeholders) - self._callbacks = self._parse_tree_builder.create_callback(self.options.transformer) - - def _build_parser(self): + def _build_lexer(self, dont_ignore: bool=False) -> BasicLexer: + lexer_conf = self.lexer_conf + if dont_ignore: + from copy import copy + lexer_conf = copy(lexer_conf) + lexer_conf.ignore = () + return BasicLexer(lexer_conf) + + def _prepare_callbacks(self) -> None: + self._callbacks = {} + # we don't need these callbacks if we aren't building a tree + if self.options.ambiguity != 'forest': + self._parse_tree_builder = ParseTreeBuilder( + self.rules, + self.options.tree_class or Tree, + self.options.propagate_positions, + self.options.parser != 'lalr' and self.options.ambiguity == 'explicit', + self.options.maybe_placeholders + ) + self._callbacks = self._parse_tree_builder.create_callback(self.options.transformer) + self._callbacks.update(_get_lexer_callbacks(self.options.transformer, self.terminals)) + + def _build_parser(self) -> "ParsingFrontend": self._prepare_callbacks() + _validate_frontend_args(self.options.parser, self.options.lexer) parser_conf = ParserConf(self.rules, self._callbacks, self.options.start) - return self.parser_class(self.lexer_conf, parser_conf, self.re, options=self.options) - - def save(self, f): + return _construct_parsing_frontend( + self.options.parser, + self.options.lexer, + self.lexer_conf, + parser_conf, + options=self.options + ) + + def save(self, f, exclude_options: Collection[str] = ()) -> None: + """Saves the instance into the given file object + + Useful for caching and multiprocessing. + """ data, m = self.memo_serialize([TerminalDef, Rule]) - pickle.dump({'data': data, 'memo': m}, f) + if exclude_options: + data["options"] = {n: v for n, v in data["options"].items() if n not in exclude_options} + pickle.dump({'data': data, 'memo': m}, f, protocol=pickle.HIGHEST_PROTOCOL) @classmethod - def load(cls, f): + def load(cls: Type[_T], f) -> _T: + """Loads an instance from the given file object + + Useful for caching and multiprocessing. + """ inst = cls.__new__(cls) return inst._load(f) - def _load(self, f, transformer=None, postlex=None): + def _deserialize_lexer_conf(self, data: Dict[str, Any], memo: Dict[int, Union[TerminalDef, Rule]], options: LarkOptions) -> LexerConf: + lexer_conf = LexerConf.deserialize(data['lexer_conf'], memo) + lexer_conf.callbacks = options.lexer_callbacks or {} + lexer_conf.re_module = regex if options.regex else re + lexer_conf.use_bytes = options.use_bytes + lexer_conf.g_regex_flags = options.g_regex_flags + lexer_conf.skip_validation = True + lexer_conf.postlex = options.postlex + return lexer_conf + + def _load(self: _T, f: Any, **kwargs) -> _T: if isinstance(f, dict): d = f else: d = pickle.load(f) - memo = d['memo'] + memo_json = d['memo'] data = d['data'] - assert memo - memo = SerializeMemoizer.deserialize(memo, {'Rule': Rule, 'TerminalDef': TerminalDef}, {}) + assert memo_json + memo = SerializeMemoizer.deserialize(memo_json, {'Rule': Rule, 'TerminalDef': TerminalDef}, {}) options = dict(data['options']) - if transformer is not None: - options['transformer'] = transformer - if postlex is not None: - options['postlex'] = postlex + if (set(kwargs) - _LOAD_ALLOWED_OPTIONS) & set(LarkOptions._defaults): + raise ConfigurationError("Some options are not allowed when loading a Parser: {}" + .format(set(kwargs) - _LOAD_ALLOWED_OPTIONS)) + options.update(kwargs) self.options = LarkOptions.deserialize(options, memo) - self.re = regex if self.options.regex else re self.rules = [Rule.deserialize(r, memo) for r in data['rules']] - self.source = '' + self.source_path = '' + _validate_frontend_args(self.options.parser, self.options.lexer) + self.lexer_conf = self._deserialize_lexer_conf(data['parser'], memo, self.options) + self.terminals = self.lexer_conf.terminals self._prepare_callbacks() - self.parser = self.parser_class.deserialize(data['parser'], memo, self._callbacks, self.options.postlex, self.re) + self._terminals_dict = {t.name: t for t in self.terminals} + self.parser = _deserialize_parsing_frontend( + data['parser'], + memo, + self.lexer_conf, + self._callbacks, + self.options, # Not all, but multiple attributes are used + ) return self @classmethod - def _load_from_dict(cls, data, memo, transformer=None, postlex=None): + def _load_from_dict(cls, data, memo, **kwargs): inst = cls.__new__(cls) - return inst._load({'data': data, 'memo': memo}, transformer, postlex) + return inst._load({'data': data, 'memo': memo}, **kwargs) @classmethod - def open(cls, grammar_filename, rel_to=None, **options): + def open(cls: Type[_T], grammar_filename: str, rel_to: Optional[str]=None, **options) -> _T: """Create an instance of Lark with the grammar given by its filename - If rel_to is provided, the function will find the grammar filename in relation to it. + If ``rel_to`` is provided, the function will find the grammar filename in relation to it. Example: @@ -361,45 +566,83 @@ def open(cls, grammar_filename, rel_to=None, **options): with open(grammar_filename, encoding='utf8') as f: return cls(f, **options) + @classmethod + def open_from_package(cls: Type[_T], package: str, grammar_path: str, search_paths: 'Sequence[str]'=[""], **options) -> _T: + """Create an instance of Lark with the grammar loaded from within the package `package`. + This allows grammar loading from zipapps. + + Imports in the grammar will use the `package` and `search_paths` provided, through `FromPackageLoader` + + Example: + + Lark.open_from_package(__name__, "example.lark", ("grammars",), parser=...) + """ + package_loader = FromPackageLoader(package, search_paths) + full_path, text = package_loader(None, grammar_path) + options.setdefault('source_path', full_path) + options.setdefault('import_paths', []) + options['import_paths'].append(package_loader) + return cls(text, **options) + def __repr__(self): - return 'Lark(open(%r), parser=%r, lexer=%r, ...)' % (self.source, self.options.parser, self.options.lexer) + return 'Lark(open(%r), parser=%r, lexer=%r, ...)' % (self.source_path, self.options.parser, self.options.lexer) - def lex(self, text): - "Only lex (and postlex) the text, without parsing it. Only relevant when lexer='standard'" - if not hasattr(self, 'lexer'): - self.lexer = self._build_lexer() - stream = self.lexer.lex(text) + def lex(self, text: str, dont_ignore: bool=False) -> Iterator[Token]: + """Only lex (and postlex) the text, without parsing it. Only relevant when lexer='basic' + + When dont_ignore=True, the lexer will return all tokens, even those marked for %ignore. + + :raises UnexpectedCharacters: In case the lexer cannot find a suitable match. + """ + lexer: Lexer + if not hasattr(self, 'lexer') or dont_ignore: + lexer = self._build_lexer(dont_ignore) + else: + lexer = self.lexer + lexer_thread = LexerThread.from_text(lexer, text) + stream = lexer_thread.lex(None) if self.options.postlex: return self.options.postlex.process(stream) return stream - def get_terminal(self, name): - "Get information about a terminal" + def get_terminal(self, name: str) -> TerminalDef: + """Get information about a terminal""" return self._terminals_dict[name] - def parse(self, text, start=None, on_error=None): + def parse_interactive(self, text: Optional[str]=None, start: Optional[str]=None) -> 'InteractiveParser': + """Start an interactive parsing session. + + Parameters: + text (str, optional): Text to be parsed. Required for ``resume_parse()``. + start (str, optional): Start symbol + + Returns: + A new InteractiveParser instance. + + See Also: ``Lark.parse()`` + """ + return self.parser.parse_interactive(text, start=start) + + def parse(self, text: str, start: Optional[str]=None, on_error: 'Optional[Callable[[UnexpectedInput], bool]]'=None) -> 'ParseTree': """Parse the given text, according to the options provided. Parameters: - start: str - required if Lark was given multiple possible start symbols (using the start option). - on_error: function - if provided, will be called on UnexpectedToken error. Return true to resume parsing. LALR only. + text (str): Text to be parsed. + start (str, optional): Required if Lark was given multiple possible start symbols (using the start option). + on_error (function, optional): if provided, will be called on UnexpectedToken error. Return true to resume parsing. + LALR only. See examples/advanced/error_handling.py for an example of how to use on_error. + + Returns: + If a transformer is supplied to ``__init__``, returns whatever is the + result of the transformation. Otherwise, returns a Tree instance. + + :raises UnexpectedInput: On a parse error, one of these sub-exceptions will rise: + ``UnexpectedCharacters``, ``UnexpectedToken``, or ``UnexpectedEOF``. + For convenience, these sub-exceptions also inherit from ``ParserError`` and ``LexerError``. - Returns a tree, unless specified otherwise. """ - try: - return self.parser.parse(text, start=start) - except UnexpectedToken as e: - if on_error is None: - raise - - while True: - if not on_error(e): - raise e - try: - return e.puppet.resume_parse() - except UnexpectedToken as e2: - e = e2 + return self.parser.parse(text, start=start, on_error=on_error) ###} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/lexer.py b/conda_lock/_vendor/poetry/core/_vendor/lark/lexer.py index bff5de9e8..5e6d6d406 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/lexer.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/lexer.py @@ -1,17 +1,36 @@ -## Lexer Implementation +# Lexer Implementation +from abc import abstractmethod, ABC import re - -from .utils import Str, classify, get_regexp_width, Py36, Serialize +from contextlib import suppress +from typing import ( + TypeVar, Type, List, Dict, Iterator, Collection, Callable, Optional, FrozenSet, Any, + Pattern as REPattern, ClassVar, TYPE_CHECKING, overload +) +from types import ModuleType +import warnings +if TYPE_CHECKING: + from .common import LexerConf + +from .utils import classify, get_regexp_width, Serialize from .exceptions import UnexpectedCharacters, LexError, UnexpectedToken +from .grammar import TOKEN_DEFAULT_PRIORITY ###{standalone +from copy import copy + + +class Pattern(Serialize, ABC): -class Pattern(Serialize): + value: str + flags: Collection[str] + raw: Optional[str] + type: ClassVar[str] - def __init__(self, value, flags=()): + def __init__(self, value: str, flags: Collection[str]=(), raw: Optional[str]=None) -> None: self.value = value self.flags = frozenset(flags) + self.raw = raw def __repr__(self): return repr(self.to_regexp()) @@ -19,45 +38,53 @@ def __repr__(self): # Pattern Hashing assumes all subclasses have a different priority! def __hash__(self): return hash((type(self), self.value, self.flags)) + def __eq__(self, other): return type(self) == type(other) and self.value == other.value and self.flags == other.flags - def to_regexp(self): + @abstractmethod + def to_regexp(self) -> str: + raise NotImplementedError() + + @property + @abstractmethod + def min_width(self) -> int: raise NotImplementedError() - if Py36: - # Python 3.6 changed syntax for flags in regular expression - def _get_flags(self, value): - for f in self.flags: - value = ('(?%s:%s)' % (f, value)) - return value + @property + @abstractmethod + def max_width(self) -> int: + raise NotImplementedError() - else: - def _get_flags(self, value): - for f in self.flags: - value = ('(?%s)' % f) + value - return value + def _get_flags(self, value): + for f in self.flags: + value = ('(?%s:%s)' % (f, value)) + return value class PatternStr(Pattern): __serialize_fields__ = 'value', 'flags' - type = "str" + type: ClassVar[str] = "str" - def to_regexp(self): + def to_regexp(self) -> str: return self._get_flags(re.escape(self.value)) @property - def min_width(self): + def min_width(self) -> int: + return len(self.value) + + @property + def max_width(self) -> int: return len(self.value) - max_width = min_width + class PatternRE(Pattern): __serialize_fields__ = 'value', 'flags', '_width' - type = "re" + type: ClassVar[str] = "re" - def to_regexp(self): + def to_regexp(self) -> str: return self._get_flags(self.value) _width = None @@ -67,10 +94,11 @@ def _get_width(self): return self._width @property - def min_width(self): + def min_width(self) -> int: return self._get_width()[0] + @property - def max_width(self): + def max_width(self) -> int: return self._get_width()[1] @@ -78,7 +106,11 @@ class TerminalDef(Serialize): __serialize_fields__ = 'name', 'pattern', 'priority' __serialize_namespace__ = PatternStr, PatternRE - def __init__(self, name, pattern, priority=1): + name: str + pattern: Pattern + priority: int + + def __init__(self, name: str, pattern: Pattern, priority: int=TOKEN_DEFAULT_PRIORITY) -> None: assert isinstance(pattern, Pattern), pattern self.name = name self.pattern = pattern @@ -87,69 +119,166 @@ def __init__(self, name, pattern, priority=1): def __repr__(self): return '%s(%r, %r)' % (type(self).__name__, self.name, self.pattern) + def user_repr(self) -> str: + if self.name.startswith('__'): # We represent a generated terminal + return self.pattern.raw or self.name + else: + return self.name + +_T = TypeVar('_T', bound="Token") + +class Token(str): + """A string with meta-information, that is produced by the lexer. + + When parsing text, the resulting chunks of the input that haven't been discarded, + will end up in the tree as Token instances. The Token class inherits from Python's ``str``, + so normal string comparisons and operations will work as expected. + + Attributes: + type: Name of the token (as specified in grammar) + value: Value of the token (redundant, as ``token.value == token`` will always be true) + start_pos: The index of the token in the text + line: The line of the token in the text (starting with 1) + column: The column of the token in the text (starting with 1) + end_line: The line where the token ends + end_column: The next column after the end of the token. For example, + if the token is a single character with a column value of 4, + end_column will be 5. + end_pos: the index where the token ends (basically ``start_pos + len(token)``) + """ + __slots__ = ('type', 'start_pos', 'value', 'line', 'column', 'end_line', 'end_column', 'end_pos') + + __match_args__ = ('type', 'value') + + type: str + start_pos: Optional[int] + value: Any + line: Optional[int] + column: Optional[int] + end_line: Optional[int] + end_column: Optional[int] + end_pos: Optional[int] + + + @overload + def __new__( + cls, + type: str, + value: Any, + start_pos: Optional[int]=None, + line: Optional[int]=None, + column: Optional[int]=None, + end_line: Optional[int]=None, + end_column: Optional[int]=None, + end_pos: Optional[int]=None + ) -> 'Token': + ... + + @overload + def __new__( + cls, + type_: str, + value: Any, + start_pos: Optional[int]=None, + line: Optional[int]=None, + column: Optional[int]=None, + end_line: Optional[int]=None, + end_column: Optional[int]=None, + end_pos: Optional[int]=None + ) -> 'Token': ... + + def __new__(cls, *args, **kwargs): + if "type_" in kwargs: + warnings.warn("`type_` is deprecated use `type` instead", DeprecationWarning) + + if "type" in kwargs: + raise TypeError("Error: using both 'type' and the deprecated 'type_' as arguments.") + kwargs["type"] = kwargs.pop("type_") + + return cls._future_new(*args, **kwargs) -class Token(Str): - __slots__ = ('type', 'pos_in_stream', 'value', 'line', 'column', 'end_line', 'end_column', 'end_pos') - - def __new__(cls, type_, value, pos_in_stream=None, line=None, column=None, end_line=None, end_column=None, end_pos=None): - try: - self = super(Token, cls).__new__(cls, value) - except UnicodeDecodeError: - value = value.decode('latin1') - self = super(Token, cls).__new__(cls, value) - - self.type = type_ - self.pos_in_stream = pos_in_stream - self.value = value - self.line = line - self.column = column - self.end_line = end_line - self.end_column = end_column - self.end_pos = end_pos - return self - - def update(self, type_=None, value=None): + @classmethod + def _future_new(cls, type, value, start_pos=None, line=None, column=None, end_line=None, end_column=None, end_pos=None): + inst = super(Token, cls).__new__(cls, value) + + inst.type = type + inst.start_pos = start_pos + inst.value = value + inst.line = line + inst.column = column + inst.end_line = end_line + inst.end_column = end_column + inst.end_pos = end_pos + return inst + + @overload + def update(self, type: Optional[str]=None, value: Optional[Any]=None) -> 'Token': + ... + + @overload + def update(self, type_: Optional[str]=None, value: Optional[Any]=None) -> 'Token': + ... + + def update(self, *args, **kwargs): + if "type_" in kwargs: + warnings.warn("`type_` is deprecated use `type` instead", DeprecationWarning) + + if "type" in kwargs: + raise TypeError("Error: using both 'type' and the deprecated 'type_' as arguments.") + kwargs["type"] = kwargs.pop("type_") + + return self._future_update(*args, **kwargs) + + def _future_update(self, type: Optional[str]=None, value: Optional[Any]=None) -> 'Token': return Token.new_borrow_pos( - type_ if type_ is not None else self.type, + type if type is not None else self.type, value if value is not None else self.value, self ) @classmethod - def new_borrow_pos(cls, type_, value, borrow_t): - return cls(type_, value, borrow_t.pos_in_stream, borrow_t.line, borrow_t.column, borrow_t.end_line, borrow_t.end_column, borrow_t.end_pos) + def new_borrow_pos(cls: Type[_T], type_: str, value: Any, borrow_t: 'Token') -> _T: + return cls(type_, value, borrow_t.start_pos, borrow_t.line, borrow_t.column, borrow_t.end_line, borrow_t.end_column, borrow_t.end_pos) def __reduce__(self): - return (self.__class__, (self.type, self.value, self.pos_in_stream, self.line, self.column, )) + return (self.__class__, (self.type, self.value, self.start_pos, self.line, self.column)) def __repr__(self): - return 'Token(%s, %r)' % (self.type, self.value) + return 'Token(%r, %r)' % (self.type, self.value) def __deepcopy__(self, memo): - return Token(self.type, self.value, self.pos_in_stream, self.line, self.column) + return Token(self.type, self.value, self.start_pos, self.line, self.column) def __eq__(self, other): if isinstance(other, Token) and self.type != other.type: return False - return Str.__eq__(self, other) + return str.__eq__(self, other) - __hash__ = Str.__hash__ + __hash__ = str.__hash__ class LineCounter: - def __init__(self): - self.newline_char = '\n' + __slots__ = 'char_pos', 'line', 'column', 'line_start_pos', 'newline_char' + + def __init__(self, newline_char): + self.newline_char = newline_char self.char_pos = 0 self.line = 1 self.column = 1 self.line_start_pos = 0 - def feed(self, token, test_newline=True): + def __eq__(self, other): + if not isinstance(other, LineCounter): + return NotImplemented + + return self.char_pos == other.char_pos and self.newline_char == other.newline_char + + def feed(self, token: Token, test_newline=True): """Consume a token and calculate the new line & column. - As an optional optimization, set test_newline=False is token doesn't contain a newline. + As an optional optimization, set test_newline=False if token doesn't contain a newline. """ if test_newline: newlines = token.count(self.newline_char) @@ -160,62 +289,18 @@ def feed(self, token, test_newline=True): self.char_pos += len(token) self.column = self.char_pos - self.line_start_pos + 1 -class _Lex: - "Built to serve both Lexer and ContextualLexer" - def __init__(self, lexer, state=None): - self.lexer = lexer - self.state = state - - def lex(self, stream, newline_types, ignore_types): - newline_types = frozenset(newline_types) - ignore_types = frozenset(ignore_types) - line_ctr = LineCounter() - last_token = None - - while line_ctr.char_pos < len(stream): - lexer = self.lexer - res = lexer.match(stream, line_ctr.char_pos) - if not res: - allowed = {v for m, tfi in lexer.mres for v in tfi.values()} - ignore_types - if not allowed: - allowed = {""} - raise UnexpectedCharacters(stream, line_ctr.char_pos, line_ctr.line, line_ctr.column, allowed=allowed, state=self.state, token_history=last_token and [last_token]) - - value, type_ = res - - if type_ not in ignore_types: - t = Token(type_, value, line_ctr.char_pos, line_ctr.line, line_ctr.column) - line_ctr.feed(value, type_ in newline_types) - t.end_line = line_ctr.line - t.end_column = line_ctr.column - t.end_pos = line_ctr.char_pos - if t.type in lexer.callback: - t = lexer.callback[t.type](t) - if not isinstance(t, Token): - raise ValueError("Callbacks must return a token (returned %r)" % t) - yield t - last_token = t - else: - if type_ in lexer.callback: - t2 = Token(type_, value, line_ctr.char_pos, line_ctr.line, line_ctr.column) - lexer.callback[type_](t2) - line_ctr.feed(value, type_ in newline_types) - - - class UnlessCallback: - def __init__(self, mres): - self.mres = mres + def __init__(self, scanner): + self.scanner = scanner def __call__(self, t): - for mre, type_from_index in self.mres: - m = mre.match(t.value) - if m: - t.type = type_from_index[m.lastindex] - break + res = self.scanner.match(t.value, 0) + if res: + _value, t.type = res return t + class CallChain: def __init__(self, callback1, callback2, cond): self.callback1 = callback1 @@ -227,53 +312,72 @@ def __call__(self, t): return self.callback2(t) if self.cond(t2) else t2 +def _get_match(re_, regexp, s, flags): + m = re_.match(regexp, s, flags) + if m: + return m.group(0) - - -def _create_unless(terminals, g_regex_flags, re_): +def _create_unless(terminals, g_regex_flags, re_, use_bytes): tokens_by_type = classify(terminals, lambda t: type(t.pattern)) assert len(tokens_by_type) <= 2, tokens_by_type.keys() embedded_strs = set() callback = {} for retok in tokens_by_type.get(PatternRE, []): - unless = [] # {} + unless = [] for strtok in tokens_by_type.get(PatternStr, []): - if strtok.priority > retok.priority: + if strtok.priority != retok.priority: continue s = strtok.pattern.value - m = re_.match(retok.pattern.to_regexp(), s, g_regex_flags) - if m and m.group(0) == s: + if s == _get_match(re_, retok.pattern.to_regexp(), s, g_regex_flags): unless.append(strtok) if strtok.pattern.flags <= retok.pattern.flags: embedded_strs.add(strtok) if unless: - callback[retok.name] = UnlessCallback(build_mres(unless, g_regex_flags, re_, match_whole=True)) + callback[retok.name] = UnlessCallback(Scanner(unless, g_regex_flags, re_, match_whole=True, use_bytes=use_bytes)) - terminals = [t for t in terminals if t not in embedded_strs] - return terminals, callback + new_terminals = [t for t in terminals if t not in embedded_strs] + return new_terminals, callback -def _build_mres(terminals, max_size, g_regex_flags, match_whole, re_): - # Python sets an unreasonable group limit (currently 100) in its re module - # Worse, the only way to know we reached it is by catching an AssertionError! - # This function recursively tries less and less groups until it's successful. - postfix = '$' if match_whole else '' - mres = [] - while terminals: - try: - mre = re_.compile(u'|'.join(u'(?P<%s>%s)'%(t.name, t.pattern.to_regexp()+postfix) for t in terminals[:max_size]), g_regex_flags) - except AssertionError: # Yes, this is what Python provides us.. :/ - return _build_mres(terminals, max_size//2, g_regex_flags, match_whole, re_) +class Scanner: + def __init__(self, terminals, g_regex_flags, re_, use_bytes, match_whole=False): + self.terminals = terminals + self.g_regex_flags = g_regex_flags + self.re_ = re_ + self.use_bytes = use_bytes + self.match_whole = match_whole + + self.allowed_types = {t.name for t in self.terminals} + + self._mres = self._build_mres(terminals, len(terminals)) + + def _build_mres(self, terminals, max_size): + # Python sets an unreasonable group limit (currently 100) in its re module + # Worse, the only way to know we reached it is by catching an AssertionError! + # This function recursively tries less and less groups until it's successful. + postfix = '$' if self.match_whole else '' + mres = [] + while terminals: + pattern = u'|'.join(u'(?P<%s>%s)' % (t.name, t.pattern.to_regexp() + postfix) for t in terminals[:max_size]) + if self.use_bytes: + pattern = pattern.encode('latin-1') + try: + mre = self.re_.compile(pattern, self.g_regex_flags) + except AssertionError: # Yes, this is what Python provides us.. :/ + return self._build_mres(terminals, max_size//2) - # terms_from_name = {t.name: t for t in terminals[:max_size]} - mres.append((mre, {i:n for n,i in mre.groupindex.items()} )) - terminals = terminals[max_size:] - return mres + mres.append(mre) + terminals = terminals[max_size:] + return mres + + def match(self, text, pos): + for mre in self._mres: + m = mre.match(text, pos) + if m: + return m.group(0), m.lastgroup -def build_mres(terminals, g_regex_flags, re_, match_whole=False): - return _build_mres(terminals, len(terminals), g_regex_flags, match_whole, re_) -def _regexp_has_newline(r): +def _regexp_has_newline(r: str): r"""Expressions that may indicate newlines in a regexp: - newlines (\n) - escaped newline (\\n) @@ -283,46 +387,111 @@ def _regexp_has_newline(r): """ return '\n' in r or '\\n' in r or '\\s' in r or '[^' in r or ('(?s' in r and '.' in r) -class Lexer(object): + +class LexerState: + """Represents the current state of the lexer as it scans the text + (Lexer objects are only instanciated per grammar, not per text) + """ + + __slots__ = 'text', 'line_ctr', 'last_token' + + def __init__(self, text, line_ctr=None, last_token=None): + self.text = text + self.line_ctr = line_ctr or LineCounter(b'\n' if isinstance(text, bytes) else '\n') + self.last_token = last_token + + def __eq__(self, other): + if not isinstance(other, LexerState): + return NotImplemented + + return self.text is other.text and self.line_ctr == other.line_ctr and self.last_token == other.last_token + + def __copy__(self): + return type(self)(self.text, copy(self.line_ctr), self.last_token) + + +class LexerThread: + """A thread that ties a lexer instance and a lexer state, to be used by the parser + """ + + def __init__(self, lexer: 'Lexer', lexer_state: LexerState): + self.lexer = lexer + self.state = lexer_state + + @classmethod + def from_text(cls, lexer: 'Lexer', text: str): + return cls(lexer, LexerState(text)) + + def lex(self, parser_state): + return self.lexer.lex(self.state, parser_state) + + def __copy__(self): + return type(self)(self.lexer, copy(self.state)) + + _Token = Token + + +_Callback = Callable[[Token], Token] + +class Lexer(ABC): """Lexer interface Method Signatures: - lex(self, stream) -> Iterator[Token] + lex(self, lexer_state, parser_state) -> Iterator[Token] """ - lex = NotImplemented + @abstractmethod + def lex(self, lexer_state: LexerState, parser_state: Any) -> Iterator[Token]: + return NotImplemented + + def make_lexer_state(self, text): + "Deprecated" + return LexerState(text) -class TraditionalLexer(Lexer): +class BasicLexer(Lexer): - def __init__(self, terminals, re_, ignore=(), user_callbacks={}, g_regex_flags=0): + terminals: Collection[TerminalDef] + ignore_types: FrozenSet[str] + newline_types: FrozenSet[str] + user_callbacks: Dict[str, _Callback] + callback: Dict[str, _Callback] + re: ModuleType + + def __init__(self, conf: 'LexerConf') -> None: + terminals = list(conf.terminals) assert all(isinstance(t, TerminalDef) for t in terminals), terminals - terminals = list(terminals) + self.re = conf.re_module - self.re = re_ - # Sanitization - for t in terminals: - try: - self.re.compile(t.pattern.to_regexp(), g_regex_flags) - except self.re.error: - raise LexError("Cannot compile token %s: %s" % (t.name, t.pattern)) + if not conf.skip_validation: + # Sanitization + for t in terminals: + try: + self.re.compile(t.pattern.to_regexp(), conf.g_regex_flags) + except self.re.error: + raise LexError("Cannot compile token %s: %s" % (t.name, t.pattern)) - if t.pattern.min_width == 0: - raise LexError("Lexer does not allow zero-width terminals. (%s: %s)" % (t.name, t.pattern)) + if t.pattern.min_width == 0: + raise LexError("Lexer does not allow zero-width terminals. (%s: %s)" % (t.name, t.pattern)) - assert set(ignore) <= {t.name for t in terminals} + if not (set(conf.ignore) <= {t.name for t in terminals}): + raise LexError("Ignore terminals are not defined: %s" % (set(conf.ignore) - {t.name for t in terminals})) # Init - self.newline_types = [t.name for t in terminals if _regexp_has_newline(t.pattern.to_regexp())] - self.ignore_types = list(ignore) + self.newline_types = frozenset(t.name for t in terminals if _regexp_has_newline(t.pattern.to_regexp())) + self.ignore_types = frozenset(conf.ignore) - terminals.sort(key=lambda x:(-x.priority, -x.pattern.max_width, -len(x.pattern.value), x.name)) + terminals.sort(key=lambda x: (-x.priority, -x.pattern.max_width, -len(x.pattern.value), x.name)) self.terminals = terminals - self.user_callbacks = user_callbacks - self.build(g_regex_flags) + self.user_callbacks = conf.callbacks + self.g_regex_flags = conf.g_regex_flags + self.use_bytes = conf.use_bytes + self.terminals_by_name = conf.terminals_by_name + + self._scanner = None - def build(self, g_regex_flags=0): - terminals, self.callback = _create_unless(self.terminals, g_regex_flags, re_=self.re) + def _build_scanner(self): + terminals, self.callback = _create_unless(self.terminals, self.g_regex_flags, self.re, self.use_bytes) assert all(self.callback.values()) for type_, f in self.user_callbacks.items(): @@ -332,64 +501,103 @@ def build(self, g_regex_flags=0): else: self.callback[type_] = f - self.mres = build_mres(terminals, g_regex_flags, self.re) + self._scanner = Scanner(terminals, self.g_regex_flags, self.re, self.use_bytes) - def match(self, stream, pos): - for mre, type_from_index in self.mres: - m = mre.match(stream, pos) - if m: - return m.group(0), type_from_index[m.lastindex] + @property + def scanner(self): + if self._scanner is None: + self._build_scanner() + return self._scanner + + def match(self, text, pos): + return self.scanner.match(text, pos) + + def lex(self, state: LexerState, parser_state: Any) -> Iterator[Token]: + with suppress(EOFError): + while True: + yield self.next_token(state, parser_state) + + def next_token(self, lex_state: LexerState, parser_state: Any=None) -> Token: + line_ctr = lex_state.line_ctr + while line_ctr.char_pos < len(lex_state.text): + res = self.match(lex_state.text, line_ctr.char_pos) + if not res: + allowed = self.scanner.allowed_types - self.ignore_types + if not allowed: + allowed = {""} + raise UnexpectedCharacters(lex_state.text, line_ctr.char_pos, line_ctr.line, line_ctr.column, + allowed=allowed, token_history=lex_state.last_token and [lex_state.last_token], + state=parser_state, terminals_by_name=self.terminals_by_name) - def lex(self, stream): - return _Lex(self).lex(stream, self.newline_types, self.ignore_types) + value, type_ = res + if type_ not in self.ignore_types: + t = Token(type_, value, line_ctr.char_pos, line_ctr.line, line_ctr.column) + line_ctr.feed(value, type_ in self.newline_types) + t.end_line = line_ctr.line + t.end_column = line_ctr.column + t.end_pos = line_ctr.char_pos + if t.type in self.callback: + t = self.callback[t.type](t) + if not isinstance(t, Token): + raise LexError("Callbacks must return a token (returned %r)" % t) + lex_state.last_token = t + return t + else: + if type_ in self.callback: + t2 = Token(type_, value, line_ctr.char_pos, line_ctr.line, line_ctr.column) + self.callback[type_](t2) + line_ctr.feed(value, type_ in self.newline_types) + # EOF + raise EOFError(self) class ContextualLexer(Lexer): - def __init__(self, terminals, states, re_, ignore=(), always_accept=(), user_callbacks={}, g_regex_flags=0): - self.re = re_ - tokens_by_name = {} - for t in terminals: - assert t.name not in tokens_by_name, t - tokens_by_name[t.name] = t + lexers: Dict[str, BasicLexer] + root_lexer: BasicLexer + + def __init__(self, conf: 'LexerConf', states: Dict[str, Collection[str]], always_accept: Collection[str]=()) -> None: + terminals = list(conf.terminals) + terminals_by_name = conf.terminals_by_name + + trad_conf = copy(conf) + trad_conf.terminals = terminals - lexer_by_tokens = {} + lexer_by_tokens: Dict[FrozenSet[str], BasicLexer] = {} self.lexers = {} for state, accepts in states.items(): key = frozenset(accepts) try: lexer = lexer_by_tokens[key] except KeyError: - accepts = set(accepts) | set(ignore) | set(always_accept) - state_tokens = [tokens_by_name[n] for n in accepts if n and n in tokens_by_name] - lexer = TraditionalLexer(state_tokens, re_=self.re, ignore=ignore, user_callbacks=user_callbacks, g_regex_flags=g_regex_flags) + accepts = set(accepts) | set(conf.ignore) | set(always_accept) + lexer_conf = copy(trad_conf) + lexer_conf.terminals = [terminals_by_name[n] for n in accepts if n in terminals_by_name] + lexer = BasicLexer(lexer_conf) lexer_by_tokens[key] = lexer self.lexers[state] = lexer - self.root_lexer = TraditionalLexer(terminals, re_=self.re, ignore=ignore, user_callbacks=user_callbacks, g_regex_flags=g_regex_flags) + assert trad_conf.terminals is terminals + self.root_lexer = BasicLexer(trad_conf) - def lex(self, stream, get_parser_state): - parser_state = get_parser_state() - l = _Lex(self.lexers[parser_state], parser_state) + def lex(self, lexer_state: LexerState, parser_state: Any) -> Iterator[Token]: try: - for x in l.lex(stream, self.root_lexer.newline_types, self.root_lexer.ignore_types): - yield x - parser_state = get_parser_state() - l.lexer = self.lexers[parser_state] - l.state = parser_state # For debug only, no need to worry about multithreading + while True: + lexer = self.lexers[parser_state.position] + yield lexer.next_token(lexer_state, parser_state) + except EOFError: + pass except UnexpectedCharacters as e: - # In the contextual lexer, UnexpectedCharacters can mean that the terminal is defined, - # but not in the current context. + # In the contextual lexer, UnexpectedCharacters can mean that the terminal is defined, but not in the current context. # This tests the input against the global context, to provide a nicer error. - root_match = self.root_lexer.match(stream, e.pos_in_stream) - if not root_match: - raise - - value, type_ = root_match - t = Token(type_, value, e.pos_in_stream, e.line, e.column) - raise UnexpectedToken(t, e.allowed, state=e.state) + try: + last_token = lexer_state.last_token # Save last_token. Calling root_lexer.next_token will change this to the wrong token + token = self.root_lexer.next_token(lexer_state, parser_state) + raise UnexpectedToken(token, e.allowed, state=parser_state, token_history=[last_token], terminals_by_name=self.root_lexer.terminals_by_name) + except UnexpectedCharacters: + raise e # Raise the original UnexpectedCharacters. The root lexer raises it with the wrong expected set. ###} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/load_grammar.py b/conda_lock/_vendor/poetry/core/_vendor/lark/load_grammar.py index 407d8d16d..d4f553c50 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/load_grammar.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/load_grammar.py @@ -1,26 +1,30 @@ -"Parses and creates Grammar objects" - +"""Parses and creates Grammar objects""" +import hashlib import os.path import sys +from collections import namedtuple from copy import copy, deepcopy -from io import open +import pkgutil +from ast import literal_eval +from contextlib import suppress +from typing import List, Tuple, Union, Callable, Dict, Optional, Sequence -from .utils import bfs, eval_escaping +from .utils import bfs, logger, classify_bool, is_id_continue, is_id_start, bfs_all_unique, small_factors from .lexer import Token, TerminalDef, PatternStr, PatternRE from .parse_tree_builder import ParseTreeBuilder -from .parser_frontends import LALR_TraditionalLexer +from .parser_frontends import ParsingFrontend from .common import LexerConf, ParserConf -from .grammar import RuleOptions, Rule, Terminal, NonTerminal, Symbol -from .utils import classify, suppress, dedup_list, Str -from .exceptions import GrammarError, UnexpectedCharacters, UnexpectedToken +from .grammar import RuleOptions, Rule, Terminal, NonTerminal, Symbol, TOKEN_DEFAULT_PRIORITY +from .utils import classify, dedup_list +from .exceptions import GrammarError, UnexpectedCharacters, UnexpectedToken, ParseError, UnexpectedInput from .tree import Tree, SlottedTree as ST from .visitors import Transformer, Visitor, v_args, Transformer_InPlace, Transformer_NonRecursive inline_args = v_args(inline=True) __path__ = os.path.dirname(__file__) -IMPORT_PATHS = [os.path.join(__path__, 'grammars')] +IMPORT_PATHS = ['grammars'] EXT = '.lark' @@ -82,16 +86,21 @@ '_DOT': r'\.(?!\.)', '_DOTDOT': r'\.\.', 'TILDE': '~', - 'RULE': '!?[_?]?[a-z][_a-z0-9]*', + 'RULE_MODIFIERS': '(!|![?]?|[?]!?)(?=[_a-z])', + 'RULE': '_?[a-z][_a-z0-9]*', 'TERMINAL': '_?[A-Z][_A-Z0-9]*', 'STRING': r'"(\\"|\\\\|[^"\n])*?"i?', - 'REGEXP': r'/(?!/)(\\/|\\\\|[^/\n])*?/[%s]*' % _RE_FLAGS, + 'REGEXP': r'/(?!/)(\\/|\\\\|[^/])*?/[%s]*' % _RE_FLAGS, '_NL': r'(\r?\n)+\s*', + '_NL_OR': r'(\r?\n)+\s*\|', 'WS': r'[ \t]+', 'COMMENT': r'\s*//[^\n]*', + 'BACKSLASH': r'\\[ ]*\n', '_TO': '->', '_IGNORE': r'%ignore', + '_OVERRIDE': r'%override', '_DECLARE': r'%declare', + '_EXTEND': r'%extend', '_IMPORT': r'%import', 'NUMBER': r'[+-]?\d+', } @@ -99,19 +108,23 @@ RULES = { 'start': ['_list'], '_list': ['_item', '_list _item'], - '_item': ['rule', 'term', 'statement', '_NL'], + '_item': ['rule', 'term', 'ignore', 'import', 'declare', 'override', 'extend', '_NL'], - 'rule': ['RULE template_params _COLON expansions _NL', - 'RULE template_params _DOT NUMBER _COLON expansions _NL'], + 'rule': ['rule_modifiers RULE template_params priority _COLON expansions _NL'], + 'rule_modifiers': ['RULE_MODIFIERS', + ''], + 'priority': ['_DOT NUMBER', + ''], 'template_params': ['_LBRACE _template_params _RBRACE', ''], '_template_params': ['RULE', '_template_params _COMMA RULE'], - 'expansions': ['alias', - 'expansions _OR alias', - 'expansions _NL _OR alias'], + 'expansions': ['_expansions'], + '_expansions': ['alias', + '_expansions _OR alias', + '_expansions _NL_OR alias'], - '?alias': ['expansion _TO RULE', 'expansion'], + '?alias': ['expansion _TO nonterminal', 'expansion'], 'expansion': ['_expansion'], '_expansion': ['', '_expansion expr'], @@ -136,17 +149,21 @@ 'nonterminal': ['RULE'], '?name': ['RULE', 'TERMINAL'], + '?symbol': ['terminal', 'nonterminal'], 'maybe': ['_LBRA expansions _RBRA'], 'range': ['STRING _DOTDOT STRING'], - 'template_usage': ['RULE _LBRACE _template_args _RBRACE'], + 'template_usage': ['nonterminal _LBRACE _template_args _RBRACE'], '_template_args': ['value', '_template_args _COMMA value'], 'term': ['TERMINAL _COLON expansions _NL', 'TERMINAL _DOT NUMBER _COLON expansions _NL'], - 'statement': ['ignore', 'import', 'declare'], + 'override': ['_OVERRIDE rule', + '_OVERRIDE term'], + 'extend': ['_EXTEND rule', + '_EXTEND term'], 'ignore': ['_IGNORE expansions _NL'], 'declare': ['_DECLARE _declare_args _NL'], 'import': ['_IMPORT _import_path _NL', @@ -161,31 +178,170 @@ 'name_list': ['_name_list'], '_name_list': ['name', '_name_list _COMMA name'], - '_declare_args': ['name', '_declare_args name'], + '_declare_args': ['symbol', '_declare_args symbol'], 'literal': ['REGEXP', 'STRING'], } + +# Value 5 keeps the number of states in the lalr parser somewhat minimal +# It isn't optimal, but close to it. See PR #949 +SMALL_FACTOR_THRESHOLD = 5 +# The Threshold whether repeat via ~ are split up into different rules +# 50 is chosen since it keeps the number of states low and therefore lalr analysis time low, +# while not being to overaggressive and unnecessarily creating rules that might create shift/reduce conflicts. +# (See PR #949) +REPEAT_BREAK_THRESHOLD = 50 + + +class FindRuleSize(Transformer): + def __init__(self, keep_all_tokens): + self.keep_all_tokens = keep_all_tokens + + def _will_not_get_removed(self, sym): + if isinstance(sym, NonTerminal): + return not sym.name.startswith('_') + if isinstance(sym, Terminal): + return self.keep_all_tokens or not sym.filter_out + if sym is _EMPTY: + return False + assert False, sym + + def _args_as_int(self, args): + for a in args: + if isinstance(a, int): + yield a + elif isinstance(a, Symbol): + yield 1 if self._will_not_get_removed(a) else 0 + else: + assert False + + def expansion(self, args): + return sum(self._args_as_int(args)) + + def expansions(self, args): + return max(self._args_as_int(args)) + + @inline_args class EBNF_to_BNF(Transformer_InPlace): def __init__(self): self.new_rules = [] - self.rules_by_expr = {} + self.rules_cache = {} self.prefix = 'anon' self.i = 0 self.rule_options = None - def _add_recurse_rule(self, type_, expr): - if expr in self.rules_by_expr: - return self.rules_by_expr[expr] - - new_name = '__%s_%s_%d' % (self.prefix, type_, self.i) + def _name_rule(self, inner): + new_name = '__%s_%s_%d' % (self.prefix, inner, self.i) self.i += 1 - t = NonTerminal(new_name) - tree = ST('expansions', [ST('expansion', [expr]), ST('expansion', [t, expr])]) - self.new_rules.append((new_name, tree, self.rule_options)) - self.rules_by_expr[expr] = t + return new_name + + def _add_rule(self, key, name, expansions): + t = NonTerminal(name) + self.new_rules.append((name, expansions, self.rule_options)) + self.rules_cache[key] = t return t + def _add_recurse_rule(self, type_, expr): + try: + return self.rules_cache[expr] + except KeyError: + new_name = self._name_rule(type_) + t = NonTerminal(new_name) + tree = ST('expansions', [ + ST('expansion', [expr]), + ST('expansion', [t, expr]) + ]) + return self._add_rule(expr, new_name, tree) + + def _add_repeat_rule(self, a, b, target, atom): + """Generate a rule that repeats target ``a`` times, and repeats atom ``b`` times. + + When called recursively (into target), it repeats atom for x(n) times, where: + x(0) = 1 + x(n) = a(n) * x(n-1) + b + + Example rule when a=3, b=4: + + new_rule: target target target atom atom atom atom + + """ + key = (a, b, target, atom) + try: + return self.rules_cache[key] + except KeyError: + new_name = self._name_rule('repeat_a%d_b%d' % (a, b)) + tree = ST('expansions', [ST('expansion', [target] * a + [atom] * b)]) + return self._add_rule(key, new_name, tree) + + def _add_repeat_opt_rule(self, a, b, target, target_opt, atom): + """Creates a rule that matches atom 0 to (a*n+b)-1 times. + + When target matches n times atom, and target_opt 0 to n-1 times target_opt, + + First we generate target * i followed by target_opt, for i from 0 to a-1 + These match 0 to n*a - 1 times atom + + Then we generate target * a followed by atom * i, for i from 0 to b-1 + These match n*a to n*a + b-1 times atom + + The created rule will not have any shift/reduce conflicts so that it can be used with lalr + + Example rule when a=3, b=4: + + new_rule: target_opt + | target target_opt + | target target target_opt + + | target target target + | target target target atom + | target target target atom atom + | target target target atom atom atom + + """ + key = (a, b, target, atom, "opt") + try: + return self.rules_cache[key] + except KeyError: + new_name = self._name_rule('repeat_a%d_b%d_opt' % (a, b)) + tree = ST('expansions', [ + ST('expansion', [target]*i + [target_opt]) for i in range(a) + ] + [ + ST('expansion', [target]*a + [atom]*i) for i in range(b) + ]) + return self._add_rule(key, new_name, tree) + + def _generate_repeats(self, rule, mn, mx): + """Generates a rule tree that repeats ``rule`` exactly between ``mn`` to ``mx`` times. + """ + # For a small number of repeats, we can take the naive approach + if mx < REPEAT_BREAK_THRESHOLD: + return ST('expansions', [ST('expansion', [rule] * n) for n in range(mn, mx + 1)]) + + # For large repeat values, we break the repetition into sub-rules. + # We treat ``rule~mn..mx`` as ``rule~mn rule~0..(diff=mx-mn)``. + # We then use small_factors to split up mn and diff up into values [(a, b), ...] + # This values are used with the help of _add_repeat_rule and _add_repeat_rule_opt + # to generate a complete rule/expression that matches the corresponding number of repeats + mn_target = rule + for a, b in small_factors(mn, SMALL_FACTOR_THRESHOLD): + mn_target = self._add_repeat_rule(a, b, mn_target, rule) + if mx == mn: + return mn_target + + diff = mx - mn + 1 # We add one because _add_repeat_opt_rule generates rules that match one less + diff_factors = small_factors(diff, SMALL_FACTOR_THRESHOLD) + diff_target = rule # Match rule 1 times + diff_opt_target = ST('expansion', []) # match rule 0 times (e.g. up to 1 -1 times) + for a, b in diff_factors[:-1]: + diff_opt_target = self._add_repeat_opt_rule(a, b, diff_target, diff_opt_target, rule) + diff_target = self._add_repeat_rule(a, b, diff_target, rule) + + a, b = diff_factors[-1] + diff_opt_target = self._add_repeat_opt_rule(a, b, diff_target, diff_opt_target, rule) + + return ST('expansions', [ST('expansion', [mn_target] + [diff_opt_target])]) + def expr(self, rule, op, *args): if op.value == '?': empty = ST('expansion', []) @@ -210,24 +366,15 @@ def expr(self, rule, op, *args): mn, mx = map(int, args) if mx < mn or mn < 0: raise GrammarError("Bad Range for %s (%d..%d isn't allowed)" % (rule, mn, mx)) - return ST('expansions', [ST('expansion', [rule] * n) for n in range(mn, mx+1)]) + + return self._generate_repeats(rule, mn, mx) + assert False, op def maybe(self, rule): keep_all_tokens = self.rule_options and self.rule_options.keep_all_tokens - - def will_not_get_removed(sym): - if isinstance(sym, NonTerminal): - return not sym.name.startswith('_') - if isinstance(sym, Terminal): - return keep_all_tokens or not sym.filter_out - assert False - - if any(rule.scan_values(will_not_get_removed)): - empty = _EMPTY - else: - empty = ST('expansion', []) - + rule_size = FindRuleSize(keep_all_tokens).transform(rule) + empty = ST('expansion', [_EMPTY] * rule_size) return ST('expansions', [rule, empty]) @@ -235,12 +382,8 @@ class SimplifyRule_Visitor(Visitor): @staticmethod def _flatten(tree): - while True: - to_expand = [i for i, child in enumerate(tree.children) - if isinstance(child, Tree) and child.data == tree.data] - if not to_expand: - break - tree.expand_kids_by_index(*to_expand) + while tree.expand_kids_by_data(tree.data): + pass def expansion(self, tree): # rules_list unpacking @@ -258,9 +401,9 @@ def expansion(self, tree): for i, child in enumerate(tree.children): if isinstance(child, Tree) and child.data == 'expansions': tree.data = 'expansions' - tree.children = [self.visit(ST('expansion', [option if i==j else other - for j, other in enumerate(tree.children)])) - for option in dedup_list(child.children)] + tree.children = [self.visit(ST('expansion', [option if i == j else other + for j, other in enumerate(tree.children)])) + for option in dedup_list(child.children)] self._flatten(tree) break @@ -283,31 +426,25 @@ def expansions(self, tree): class RuleTreeToText(Transformer): def expansions(self, x): return x + def expansion(self, symbols): return symbols, None + def alias(self, x): (expansion, _alias), alias = x assert _alias is None, (alias, expansion, '-', _alias) # Double alias not allowed - return expansion, alias.value + return expansion, alias.name -@inline_args -class CanonizeTree(Transformer_InPlace): - def tokenmods(self, *args): - if len(args) == 1: - return list(args) - tokenmods, value = args - return tokenmods + [value] - class PrepareAnonTerminals(Transformer_InPlace): - "Create a unique list of anonymous terminals. Attempt to give meaningful names to them when we add them" + """Create a unique list of anonymous terminals. Attempt to give meaningful names to them when we add them""" def __init__(self, terminals): self.terminals = terminals self.term_set = {td.name for td in self.terminals} self.term_reverse = {td.pattern: td for td in terminals} self.i = 0 - + self.rule_options = None @inline_args def pattern(self, p): @@ -326,16 +463,14 @@ def pattern(self, p): try: term_name = _TERMINAL_NAMES[value] except KeyError: - if value.isalnum() and value[0].isalpha() and value.upper() not in self.term_set: - with suppress(UnicodeEncodeError): - value.upper().encode('ascii') # Make sure we don't have unicode in our terminal names - term_name = value.upper() + if value and is_id_continue(value) and is_id_start(value[0]) and value.upper() not in self.term_set: + term_name = value.upper() if term_name in self.term_set: term_name = None elif isinstance(p, PatternRE): - if p in self.term_reverse: # Kind of a wierd placement.name + if p in self.term_reverse: # Kind of a weird placement.name term_name = self.term_reverse[p].name else: assert False, p @@ -351,26 +486,31 @@ def pattern(self, p): self.term_reverse[p] = termdef self.terminals.append(termdef) - return Terminal(term_name, filter_out=isinstance(p, PatternStr)) + filter_out = False if self.rule_options and self.rule_options.keep_all_tokens else isinstance(p, PatternStr) + + return Terminal(term_name, filter_out=filter_out) + class _ReplaceSymbols(Transformer_InPlace): - " Helper for ApplyTemplates " + """Helper for ApplyTemplates""" def __init__(self): self.names = {} def value(self, c): - if len(c) == 1 and isinstance(c[0], Token) and c[0].value in self.names: - return self.names[c[0].value] + if len(c) == 1 and isinstance(c[0], Symbol) and c[0].name in self.names: + return self.names[c[0].name] return self.__default__('value', c, None) def template_usage(self, c): - if c[0] in self.names: - return self.__default__('template_usage', [self.names[c[0]].name] + c[1:], None) + name = c[0].name + if name in self.names: + return self.__default__('template_usage', [self.names[name]] + c[1:], None) return self.__default__('template_usage', c, None) + class ApplyTemplates(Transformer_InPlace): - " Apply the templates, creating new rules that represent the used templates " + """Apply the templates, creating new rules that represent the used templates""" def __init__(self, rule_defs): self.rule_defs = rule_defs @@ -378,7 +518,7 @@ def __init__(self, rule_defs): self.created_templates = set() def template_usage(self, c): - name = c[0] + name = c[0].name args = c[1:] result_name = "%s{%s}" % (name, ",".join(a.name for a in args)) if result_name not in self.created_templates: @@ -396,26 +536,63 @@ def _rfind(s, choices): return max(s.rfind(c) for c in choices) +def eval_escaping(s): + w = '' + i = iter(s) + for n in i: + w += n + if n == '\\': + try: + n2 = next(i) + except StopIteration: + raise GrammarError("Literal ended unexpectedly (bad escaping): `%r`" % s) + if n2 == '\\': + w += '\\\\' + elif n2 not in 'Uuxnftr': + w += '\\' + w += n2 + w = w.replace('\\"', '"').replace("'", "\\'") + + to_eval = "u'''%s'''" % w + try: + s = literal_eval(to_eval) + except SyntaxError as e: + raise GrammarError(s, e) + + return s def _literal_to_pattern(literal): + assert isinstance(literal, Token) v = literal.value flag_start = _rfind(v, '/"')+1 assert flag_start > 0 flags = v[flag_start:] assert all(f in _RE_FLAGS for f in flags), flags + if literal.type == 'STRING' and '\n' in v: + raise GrammarError('You cannot put newlines in string literals') + + if literal.type == 'REGEXP' and '\n' in v and 'x' not in flags: + raise GrammarError('You can only use newlines in regular expressions ' + 'with the `x` (verbose) flag') + v = v[:flag_start] assert v[0] == v[-1] and v[0] in '"/' x = v[1:-1] s = eval_escaping(x) + if s == "": + raise GrammarError("Empty terminals are not allowed (%s)" % literal) + if literal.type == 'STRING': s = s.replace('\\\\', '\\') - - return { 'STRING': PatternStr, - 'REGEXP': PatternRE }[literal.type](s, flags) + return PatternStr(s, flags, raw=literal.value) + elif literal.type == 'REGEXP': + return PatternRE(s, flags, raw=literal.value) + else: + assert False, 'Invariant failed: literal.type not in ["STRING", "REGEXP"]' @inline_args @@ -427,12 +604,15 @@ def range(self, start, end): assert start.type == end.type == 'STRING' start = start.value[1:-1] end = end.value[1:-1] - assert len(eval_escaping(start)) == len(eval_escaping(end)) == 1, (start, end, len(eval_escaping(start)), len(eval_escaping(end))) + assert len(eval_escaping(start)) == len(eval_escaping(end)) == 1 regexp = '[%s-%s]' % (start, end) return ST('pattern', [PatternRE(regexp)]) -class TerminalTreeToPattern(Transformer): +def _make_joined_pattern(regexp, flags_set): + return PatternRE(regexp, ()) + +class TerminalTreeToPattern(Transformer_NonRecursive): def pattern(self, ps): p ,= ps return p @@ -441,16 +621,20 @@ def expansion(self, items): assert items if len(items) == 1: return items[0] - if len({i.flags for i in items}) > 1: - raise GrammarError("Lark doesn't support joining terminals with conflicting flags!") - return PatternRE(''.join(i.to_regexp() for i in items), items[0].flags if items else ()) + + pattern = ''.join(i.to_regexp() for i in items) + return _make_joined_pattern(pattern, {i.flags for i in items}) def expansions(self, exps): if len(exps) == 1: return exps[0] - if len({i.flags for i in exps}) > 1: - raise GrammarError("Lark doesn't support joining terminals with conflicting flags!") - return PatternRE('(?:%s)' % ('|'.join(i.to_regexp() for i in exps)), exps[0].flags) + + # Do a bit of sorting to make sure that the longest option is returned + # (Python's re module otherwise prefers just 'l' when given (l|ll) and both could match) + exps.sort(key=lambda x: (-x.max_width, -x.min_width, -len(x.value))) + + pattern = '(?:%s)' % ('|'.join(i.to_regexp() for i in exps)) + return _make_joined_pattern(pattern, {i.flags for i in exps}) def expr(self, args): inner, op = args[:2] @@ -475,35 +659,35 @@ def alias(self, t): def value(self, v): return v[0] -class PrepareSymbols(Transformer_InPlace): + +class ValidateSymbols(Transformer_InPlace): def value(self, v): v ,= v - if isinstance(v, Tree): - return v - elif v.type == 'RULE': - return NonTerminal(Str(v.value)) - elif v.type == 'TERMINAL': - return Terminal(Str(v.value), filter_out=v.startswith('_')) - assert False + assert isinstance(v, (Tree, Symbol)) + return v -def _choice_of_rules(rules): - return ST('expansions', [ST('expansion', [Token('RULE', name)]) for name in rules]) def nr_deepcopy_tree(t): - "Deepcopy tree `t` without recursion" + """Deepcopy tree `t` without recursion""" return Transformer_NonRecursive(False).transform(t) + class Grammar: - def __init__(self, rule_defs, term_defs, ignore): + + term_defs: List[Tuple[str, Tuple[Tree, int]]] + rule_defs: List[Tuple[str, Tuple[str, ...], Tree, RuleOptions]] + ignore: List[str] + + def __init__(self, rule_defs: List[Tuple[str, Tuple[str, ...], Tree, RuleOptions]], term_defs: List[Tuple[str, Tuple[Tree, int]]], ignore: List[str]) -> None: self.term_defs = term_defs self.rule_defs = rule_defs self.ignore = ignore - def compile(self, start): + def compile(self, start, terminals_to_keep): # We change the trees in-place (to support huge grammars) # So deepcopy allows calling compile more than once. - term_defs = deepcopy(list(self.term_defs)) - rule_defs = [(n,p,nr_deepcopy_tree(t),o) for n,p,t,o in self.rule_defs] + term_defs = [(n, (nr_deepcopy_tree(t), p)) for n, (t, p) in self.term_defs] + rule_defs = [(n, p, nr_deepcopy_tree(t), o) for n, p, t, o in self.rule_defs] # =================== # Compile Terminals @@ -519,7 +703,7 @@ def compile(self, start): raise GrammarError("Terminals cannot be empty (%s)" % name) transformer = PrepareLiterals() * TerminalTreeToPattern() - terminals = [TerminalDef(name, transformer.transform( term_tree ), priority) + terminals = [TerminalDef(name, transformer.transform(term_tree), priority) for name, (term_tree, priority) in term_defs if term_tree] # ================= @@ -527,7 +711,8 @@ def compile(self, start): # ================= # 1. Pre-process terminals - transformer = PrepareLiterals() * PrepareSymbols() * PrepareAnonTerminals(terminals) # Adds to terminals + anon_tokens_transf = PrepareAnonTerminals(terminals) + transformer = PrepareLiterals() * ValidateSymbols() * anon_tokens_transf # Adds to terminals # 2. Inline Templates @@ -537,13 +722,15 @@ def compile(self, start): ebnf_to_bnf = EBNF_to_BNF() rules = [] i = 0 - while i < len(rule_defs): # We have to do it like this because rule_defs might grow due to templates + while i < len(rule_defs): # We have to do it like this because rule_defs might grow due to templates name, params, rule_tree, options = rule_defs[i] i += 1 - if len(params) != 0: # Dont transform templates + if len(params) != 0: # Dont transform templates continue - ebnf_to_bnf.rule_options = RuleOptions(keep_all_tokens=True) if options.keep_all_tokens else None + rule_options = RuleOptions(keep_all_tokens=True) if options and options.keep_all_tokens else None + ebnf_to_bnf.rule_options = rule_options ebnf_to_bnf.prefix = name + anon_tokens_transf.rule_options = rule_options tree = transformer.transform(rule_tree) res = ebnf_to_bnf.transform(tree) rules.append((name, res, options)) @@ -563,7 +750,7 @@ def compile(self, start): for i, (expansion, alias) in enumerate(expansions): if alias and name.startswith('_'): - raise GrammarError("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)" % (name, alias)) + raise GrammarError("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)"% (name, alias)) empty_indices = [x==_EMPTY for x in expansion] if any(empty_indices): @@ -573,7 +760,10 @@ def compile(self, start): else: exp_options = options - assert all(isinstance(x, Symbol) for x in expansion), expansion + for sym in expansion: + assert isinstance(sym, Symbol) + if sym.is_term and exp_options and exp_options.keep_all_tokens: + sym.filter_out = False rule = Rule(NonTerminal(name), expansion, i, alias, exp_options) compiled_rules.append(rule) @@ -592,123 +782,106 @@ def compile(self, start): # Remove duplicates compiled_rules = list(set(compiled_rules)) - # Filter out unused rules while True: c = len(compiled_rules) used_rules = {s for r in compiled_rules - for s in r.expansion - if isinstance(s, NonTerminal) - and s != r.origin} + for s in r.expansion + if isinstance(s, NonTerminal) + and s != r.origin} used_rules |= {NonTerminal(s) for s in start} - compiled_rules = [r for r in compiled_rules if r.origin in used_rules] + compiled_rules, unused = classify_bool(compiled_rules, lambda r: r.origin in used_rules) + for r in unused: + logger.debug("Unused rule: %s", r) if len(compiled_rules) == c: break # Filter out unused terminals - used_terms = {t.name for r in compiled_rules - for t in r.expansion - if isinstance(t, Terminal)} - terminals = [t for t in terminals if t.name in used_terms or t.name in self.ignore] + if terminals_to_keep != '*': + used_terms = {t.name for r in compiled_rules + for t in r.expansion + if isinstance(t, Terminal)} + terminals, unused = classify_bool(terminals, lambda t: t.name in used_terms or t.name in self.ignore or t.name in terminals_to_keep) + if unused: + logger.debug("Unused terminals: %s", [t.name for t in unused]) return terminals, compiled_rules, self.ignore +PackageResource = namedtuple('PackageResource', 'pkg_name path') -_imported_grammars = {} -def import_grammar(grammar_path, re_, base_paths=[]): - if grammar_path not in _imported_grammars: - import_paths = base_paths + IMPORT_PATHS - for import_path in import_paths: - with suppress(IOError): - joined_path = os.path.join(import_path, grammar_path) - with open(joined_path, encoding='utf8') as f: - text = f.read() - grammar = load_grammar(text, joined_path, re_) - _imported_grammars[grammar_path] = grammar - break - else: - open(grammar_path, encoding='utf8') - assert False - - return _imported_grammars[grammar_path] -def import_from_grammar_into_namespace(grammar, namespace, aliases): - """Returns all rules and terminals of grammar, prepended - with a 'namespace' prefix, except for those which are aliased. +class FromPackageLoader: """ + Provides a simple way of creating custom import loaders that load from packages via ``pkgutil.get_data`` instead of using `open`. + This allows them to be compatible even from within zip files. - imported_terms = dict(grammar.term_defs) - imported_rules = {n:(n,p,deepcopy(t),o) for n,p,t,o in grammar.rule_defs} + Relative imports are handled, so you can just freely use them. - term_defs = [] - rule_defs = [] + pkg_name: The name of the package. You can probably provide `__name__` most of the time + search_paths: All the path that will be search on absolute imports. + """ - def rule_dependencies(symbol): - if symbol.type != 'RULE': - return [] - try: - _, params, tree,_ = imported_rules[symbol] - except KeyError: - raise GrammarError("Missing symbol '%s' in grammar %s" % (symbol, namespace)) - return _find_used_symbols(tree) - set(params) + pkg_name: str + search_paths: Sequence[str] + def __init__(self, pkg_name: str, search_paths: Sequence[str]=("", )) -> None: + self.pkg_name = pkg_name + self.search_paths = search_paths + def __repr__(self): + return "%s(%r, %r)" % (type(self).__name__, self.pkg_name, self.search_paths) - def get_namespace_name(name, params): - if params is not None: - try: - return params[name] - except KeyError: - pass - try: - return aliases[name].value - except KeyError: - if name[0] == '_': - return '_%s__%s' % (namespace, name[1:]) - return '%s__%s' % (namespace, name) - - to_import = list(bfs(aliases, rule_dependencies)) - for symbol in to_import: - if symbol.type == 'TERMINAL': - term_defs.append([get_namespace_name(symbol, None), imported_terms[symbol]]) + def __call__(self, base_path: Union[None, str, PackageResource], grammar_path: str) -> Tuple[PackageResource, str]: + if base_path is None: + to_try = self.search_paths else: - assert symbol.type == 'RULE' - _, params, tree, options = imported_rules[symbol] - params_map = {p: ('%s__%s' if p[0]!='_' else '_%s__%s' ) % (namespace, p) for p in params} - for t in tree.iter_subtrees(): - for i, c in enumerate(t.children): - if isinstance(c, Token) and c.type in ('RULE', 'TERMINAL'): - t.children[i] = Token(c.type, get_namespace_name(c, params_map)) - params = [params_map[p] for p in params] # We can not rely on ordered dictionaries - rule_defs.append((get_namespace_name(symbol, params_map), params, tree, options)) + # Check whether or not the importing grammar was loaded by this module. + if not isinstance(base_path, PackageResource) or base_path.pkg_name != self.pkg_name: + # Technically false, but FileNotFound doesn't exist in python2.7, and this message should never reach the end user anyway + raise IOError() + to_try = [base_path.path] + + err = None + for path in to_try: + full_path = os.path.join(path, grammar_path) + try: + text: Optional[bytes] = pkgutil.get_data(self.pkg_name, full_path) + except IOError as e: + err = e + continue + else: + return PackageResource(self.pkg_name, full_path), (text.decode() if text else '') + raise IOError('Cannot find grammar in given paths') from err - return term_defs, rule_defs +stdlib_loader = FromPackageLoader('lark', IMPORT_PATHS) -def resolve_term_references(term_defs): - # TODO Solve with transitive closure (maybe) - term_dict = {k:t for k, (t,_p) in term_defs} - assert len(term_dict) == len(term_defs), "Same name defined twice?" +def resolve_term_references(term_dict): + # TODO Solve with transitive closure (maybe) while True: changed = False - for name, (token_tree, _p) in term_defs: + for name, token_tree in term_dict.items(): if token_tree is None: # Terminal added through %declare continue for exp in token_tree.find_data('value'): item ,= exp.children - if isinstance(item, Token): - if item.type == 'RULE': - raise GrammarError("Rules aren't allowed inside terminals (%s in %s)" % (item, name)) - if item.type == 'TERMINAL': - term_value = term_dict[item] - assert term_value is not None - exp.children[0] = term_value - changed = True + if isinstance(item, NonTerminal): + raise GrammarError("Rules aren't allowed inside terminals (%s in %s)" % (item, name)) + elif isinstance(item, Terminal): + try: + term_value = term_dict[item.name] + except KeyError: + raise GrammarError("Terminal used but not defined: %s" % item.name) + assert term_value is not None + exp.children[0] = term_value + changed = True + else: + assert isinstance(item, Tree) if not changed: break @@ -720,228 +893,531 @@ def resolve_term_references(term_defs): raise GrammarError("Recursion in terminal '%s' (recursion is only allowed in rules, not terminals)" % name) -def options_from_rule(name, params, *x): - if len(x) > 1: - priority, expansions = x - priority = int(priority) - else: - expansions ,= x - priority = None - params = [t.value for t in params.children] if params is not None else [] # For the grammar parser - - keep_all_tokens = name.startswith('!') - name = name.lstrip('!') - expand1 = name.startswith('?') - name = name.lstrip('?') - - return name, params, expansions, RuleOptions(keep_all_tokens, expand1, priority=priority, - template_source=(name if params else None)) - -def symbols_from_strcase(expansion): - return [Terminal(x, filter_out=x.startswith('_')) if x.isupper() else NonTerminal(x) for x in expansion] +def symbol_from_strcase(s): + assert isinstance(s, str) + return Terminal(s, filter_out=s.startswith('_')) if s.isupper() else NonTerminal(s) @inline_args class PrepareGrammar(Transformer_InPlace): def terminal(self, name): - return name + return Terminal(str(name), filter_out=name.startswith('_')) + def nonterminal(self, name): - return name + return NonTerminal(name.value) def _find_used_symbols(tree): assert tree.data == 'expansions' - return {t for x in tree.find_data('expansion') - for t in x.scan_values(lambda t: t.type in ('RULE', 'TERMINAL'))} + return {t.name for x in tree.find_data('expansion') + for t in x.scan_values(lambda t: isinstance(t, Symbol))} + -class GrammarLoader: - def __init__(self, re_): - self.re = re_ +def _get_parser(): + try: + return _get_parser.cache + except AttributeError: terminals = [TerminalDef(name, PatternRE(value)) for name, value in TERMINALS.items()] - rules = [options_from_rule(name, None, x) for name, x in RULES.items()] - rules = [Rule(NonTerminal(r), symbols_from_strcase(x.split()), i, None, o) for r, _p, xs, o in rules for i, x in enumerate(xs)] - callback = ParseTreeBuilder(rules, ST).create_callback() - lexer_conf = LexerConf(terminals, ['WS', 'COMMENT']) + rules = [(name.lstrip('?'), x, RuleOptions(expand1=name.startswith('?'))) + for name, x in RULES.items()] + rules = [Rule(NonTerminal(r), [symbol_from_strcase(s) for s in x.split()], i, None, o) + for r, xs, o in rules for i, x in enumerate(xs)] + callback = ParseTreeBuilder(rules, ST).create_callback() + import re + lexer_conf = LexerConf(terminals, re, ['WS', 'COMMENT', 'BACKSLASH']) parser_conf = ParserConf(rules, callback, ['start']) - self.parser = LALR_TraditionalLexer(lexer_conf, parser_conf, re_) + lexer_conf.lexer_type = 'basic' + parser_conf.parser_type = 'lalr' + _get_parser.cache = ParsingFrontend(lexer_conf, parser_conf, None) + return _get_parser.cache + +GRAMMAR_ERRORS = [ + ('Incorrect type of value', ['a: 1\n']), + ('Unclosed parenthesis', ['a: (\n']), + ('Unmatched closing parenthesis', ['a: )\n', 'a: [)\n', 'a: (]\n']), + ('Expecting rule or terminal definition (missing colon)', ['a\n', 'A\n', 'a->\n', 'A->\n', 'a A\n']), + ('Illegal name for rules or terminals', ['Aa:\n']), + ('Alias expects lowercase name', ['a: -> "a"\n']), + ('Unexpected colon', ['a::\n', 'a: b:\n', 'a: B:\n', 'a: "a":\n']), + ('Misplaced operator', ['a: b??', 'a: b(?)', 'a:+\n', 'a:?\n', 'a:*\n', 'a:|*\n']), + ('Expecting option ("|") or a new rule or terminal definition', ['a:a\n()\n']), + ('Terminal names cannot contain dots', ['A.B\n']), + ('Expecting rule or terminal definition', ['"a"\n']), + ('%import expects a name', ['%import "a"\n']), + ('%ignore expects a value', ['%ignore %import\n']), + ] + +def _translate_parser_exception(parse, e): + error = e.match_examples(parse, GRAMMAR_ERRORS, use_accepts=True) + if error: + return error + elif 'STRING' in e.expected: + return "Expecting a value" + +def _parse_grammar(text, name, start='start'): + try: + tree = _get_parser().parse(text + '\n', start) + except UnexpectedCharacters as e: + context = e.get_context(text) + raise GrammarError("Unexpected input at line %d column %d in %s: \n\n%s" % + (e.line, e.column, name, context)) + except UnexpectedToken as e: + context = e.get_context(text) + error = _translate_parser_exception(_get_parser().parse, e) + if error: + raise GrammarError("%s, at line %s column %s\n\n%s" % (error, e.line, e.column, context)) + raise + + return PrepareGrammar().transform(tree) + + +def _error_repr(error): + if isinstance(error, UnexpectedToken): + error2 = _translate_parser_exception(_get_parser().parse, error) + if error2: + return error2 + expected = ', '.join(error.accepts or error.expected) + return "Unexpected token %r. Expected one of: {%s}" % (str(error.token), expected) + else: + return str(error) - self.canonize_tree = CanonizeTree() +def _search_interactive_parser(interactive_parser, predicate): + def expand(node): + path, p = node + for choice in p.choices(): + t = Token(choice, '') + try: + new_p = p.feed_token(t) + except ParseError: # Illegal + pass + else: + yield path + (choice,), new_p - def load_grammar(self, grammar_text, grammar_name=''): - "Parse grammar_text, verify, and create Grammar object. Display nice messages on error." + for path, p in bfs_all_unique([((), interactive_parser)], expand): + if predicate(p): + return path, p - try: - tree = self.canonize_tree.transform( self.parser.parse(grammar_text+'\n') ) - except UnexpectedCharacters as e: - context = e.get_context(grammar_text) - raise GrammarError("Unexpected input at line %d column %d in %s: \n\n%s" % - (e.line, e.column, grammar_name, context)) - except UnexpectedToken as e: - context = e.get_context(grammar_text) - error = e.match_examples(self.parser.parse, { - 'Unclosed parenthesis': ['a: (\n'], - 'Umatched closing parenthesis': ['a: )\n', 'a: [)\n', 'a: (]\n'], - 'Expecting rule or terminal definition (missing colon)': ['a\n', 'a->\n', 'A->\n', 'a A\n'], - 'Alias expects lowercase name': ['a: -> "a"\n'], - 'Unexpected colon': ['a::\n', 'a: b:\n', 'a: B:\n', 'a: "a":\n'], - 'Misplaced operator': ['a: b??', 'a: b(?)', 'a:+\n', 'a:?\n', 'a:*\n', 'a:|*\n'], - 'Expecting option ("|") or a new rule or terminal definition': ['a:a\n()\n'], - '%import expects a name': ['%import "a"\n'], - '%ignore expects a value': ['%ignore %import\n'], - }) - if error: - raise GrammarError("%s at line %s column %s\n\n%s" % (error, e.line, e.column, context)) - elif 'STRING' in e.expected: - raise GrammarError("Expecting a value at line %s column %s\n\n%s" % (e.line, e.column, context)) - raise - - tree = PrepareGrammar().transform(tree) - - # Extract grammar items - defs = classify(tree.children, lambda c: c.data, lambda c: c.children) - term_defs = defs.pop('term', []) - rule_defs = defs.pop('rule', []) - statements = defs.pop('statement', []) - assert not defs - - term_defs = [td if len(td)==3 else (td[0], 1, td[1]) for td in term_defs] - term_defs = [(name.value, (t, int(p))) for name, p, t in term_defs] - rule_defs = [options_from_rule(*x) for x in rule_defs] - - # Execute statements - ignore, imports = [], {} - for (stmt,) in statements: - if stmt.data == 'ignore': - t ,= stmt.children - ignore.append(t) - elif stmt.data == 'import': - if len(stmt.children) > 1: - path_node, arg1 = stmt.children - else: - path_node, = stmt.children - arg1 = None - - if isinstance(arg1, Tree): # Multi import - dotted_path = tuple(path_node.children) - names = arg1.children - aliases = dict(zip(names, names)) # Can't have aliased multi import, so all aliases will be the same as names - else: # Single import - dotted_path = tuple(path_node.children[:-1]) - name = path_node.children[-1] # Get name from dotted path - aliases = {name: arg1 or name} # Aliases if exist - - if path_node.data == 'import_lib': # Import from library - base_paths = [] - else: # Relative import - if grammar_name == '': # Import relative to script file path if grammar is coded in script - try: - base_file = os.path.abspath(sys.modules['__main__'].__file__) - except AttributeError: - base_file = None - else: - base_file = grammar_name # Import relative to grammar file path if external grammar file - if base_file: - base_paths = [os.path.split(base_file)[0]] - else: - base_paths = [os.path.abspath(os.path.curdir)] +def find_grammar_errors(text: str, start: str='start') -> List[Tuple[UnexpectedInput, str]]: + errors = [] + def on_error(e): + errors.append((e, _error_repr(e))) - try: - import_base_paths, import_aliases = imports[dotted_path] - assert base_paths == import_base_paths, 'Inconsistent base_paths for %s.' % '.'.join(dotted_path) - import_aliases.update(aliases) - except KeyError: - imports[dotted_path] = base_paths, aliases + # recover to a new line + token_path, _ = _search_interactive_parser(e.interactive_parser.as_immutable(), lambda p: '_NL' in p.choices()) + for token_type in token_path: + e.interactive_parser.feed_token(Token(token_type, '')) + e.interactive_parser.feed_token(Token('_NL', '\n')) + return True - elif stmt.data == 'declare': - for t in stmt.children: - term_defs.append([t.value, (None, None)]) + _tree = _get_parser().parse(text + '\n', start, on_error=on_error) + + errors_by_line = classify(errors, lambda e: e[0].line) + errors = [el[0] for el in errors_by_line.values()] # already sorted + + for e in errors: + e[0].interactive_parser = None + return errors + + +def _get_mangle(prefix, aliases, base_mangle=None): + def mangle(s): + if s in aliases: + s = aliases[s] + else: + if s[0] == '_': + s = '_%s__%s' % (prefix, s[1:]) else: - assert False, stmt + s = '%s__%s' % (prefix, s) + if base_mangle is not None: + s = base_mangle(s) + return s + return mangle + +def _mangle_definition_tree(exp, mangle): + if mangle is None: + return exp + exp = deepcopy(exp) # TODO: is this needed? + for t in exp.iter_subtrees(): + for i, c in enumerate(t.children): + if isinstance(c, Symbol): + t.children[i] = c.renamed(mangle) + + return exp + +def _make_rule_tuple(modifiers_tree, name, params, priority_tree, expansions): + if modifiers_tree.children: + m ,= modifiers_tree.children + expand1 = '?' in m + if expand1 and name.startswith('_'): + raise GrammarError("Inlined rules (_rule) cannot use the ?rule modifier.") + keep_all_tokens = '!' in m + else: + keep_all_tokens = False + expand1 = False + + if priority_tree.children: + p ,= priority_tree.children + priority = int(p) + else: + priority = None + + if params is not None: + params = [t.value for t in params.children] # For the grammar parser + + return name, params, expansions, RuleOptions(keep_all_tokens, expand1, priority=priority, + template_source=(name if params else None)) + + +class Definition: + def __init__(self, is_term, tree, params=(), options=None): + self.is_term = is_term + self.tree = tree + self.params = tuple(params) + self.options = options + +class GrammarBuilder: + + global_keep_all_tokens: bool + import_paths: List[Union[str, Callable]] + used_files: Dict[str, str] + + _definitions: Dict[str, Definition] + _ignore_names: List[str] + + def __init__(self, global_keep_all_tokens: bool=False, import_paths: Optional[List[Union[str, Callable]]]=None, used_files: Optional[Dict[str, str]]=None) -> None: + self.global_keep_all_tokens = global_keep_all_tokens + self.import_paths = import_paths or [] + self.used_files = used_files or {} + + self._definitions: Dict[str, Definition] = {} + self._ignore_names: List[str] = [] + + def _grammar_error(self, is_term, msg, *names): + args = {} + for i, name in enumerate(names, start=1): + postfix = '' if i == 1 else str(i) + args['name' + postfix] = name + args['type' + postfix] = lowercase_type = ("rule", "terminal")[is_term] + args['Type' + postfix] = lowercase_type.title() + raise GrammarError(msg.format(**args)) + + def _check_options(self, is_term, options): + if is_term: + if options is None: + options = 1 + elif not isinstance(options, int): + raise GrammarError("Terminal require a single int as 'options' (e.g. priority), got %s" % (type(options),)) + else: + if options is None: + options = RuleOptions() + elif not isinstance(options, RuleOptions): + raise GrammarError("Rules require a RuleOptions instance as 'options'") + if self.global_keep_all_tokens: + options.keep_all_tokens = True + return options - # import grammars - for dotted_path, (base_paths, aliases) in imports.items(): - grammar_path = os.path.join(*dotted_path) + EXT - g = import_grammar(grammar_path, self.re, base_paths=base_paths) - new_td, new_rd = import_from_grammar_into_namespace(g, '__'.join(dotted_path), aliases) - - term_defs += new_td - rule_defs += new_rd - - # Verify correctness 1 - for name, _ in term_defs: - if name.startswith('__'): - raise GrammarError('Names starting with double-underscore are reserved (Error at %s)' % name) - - # Handle ignore tokens - # XXX A slightly hacky solution. Recognition of %ignore TERMINAL as separate comes from the lexer's - # inability to handle duplicate terminals (two names, one value) - ignore_names = [] - for t in ignore: - if t.data=='expansions' and len(t.children) == 1: + + def _define(self, name, is_term, exp, params=(), options=None, *, override=False): + if name in self._definitions: + if not override: + self._grammar_error(is_term, "{Type} '{name}' defined more than once", name) + elif override: + self._grammar_error(is_term, "Cannot override a nonexisting {type} {name}", name) + + if name.startswith('__'): + self._grammar_error(is_term, 'Names starting with double-underscore are reserved (Error at {name})', name) + + self._definitions[name] = Definition(is_term, exp, params, self._check_options(is_term, options)) + + def _extend(self, name, is_term, exp, params=(), options=None): + if name not in self._definitions: + self._grammar_error(is_term, "Can't extend {type} {name} as it wasn't defined before", name) + + d = self._definitions[name] + + if is_term != d.is_term: + self._grammar_error(is_term, "Cannot extend {type} {name} - one is a terminal, while the other is not.", name) + if tuple(params) != d.params: + self._grammar_error(is_term, "Cannot extend {type} with different parameters: {name}", name) + + if d.tree is None: + self._grammar_error(is_term, "Can't extend {type} {name} - it is abstract.", name) + + # TODO: think about what to do with 'options' + base = d.tree + + assert isinstance(base, Tree) and base.data == 'expansions' + base.children.insert(0, exp) + + def _ignore(self, exp_or_name): + if isinstance(exp_or_name, str): + self._ignore_names.append(exp_or_name) + else: + assert isinstance(exp_or_name, Tree) + t = exp_or_name + if t.data == 'expansions' and len(t.children) == 1: t2 ,= t.children if t2.data=='expansion' and len(t2.children) == 1: item ,= t2.children if item.data == 'value': item ,= item.children - if isinstance(item, Token) and item.type == 'TERMINAL': - ignore_names.append(item.value) - continue + if isinstance(item, Terminal): + # Keep terminal name, no need to create a new definition + self._ignore_names.append(item.name) + return + + name = '__IGNORE_%d'% len(self._ignore_names) + self._ignore_names.append(name) + self._definitions[name] = Definition(True, t, options=TOKEN_DEFAULT_PRIORITY) + + def _unpack_import(self, stmt, grammar_name): + if len(stmt.children) > 1: + path_node, arg1 = stmt.children + else: + path_node, = stmt.children + arg1 = None + + if isinstance(arg1, Tree): # Multi import + dotted_path = tuple(path_node.children) + names = arg1.children + aliases = dict(zip(names, names)) # Can't have aliased multi import, so all aliases will be the same as names + else: # Single import + dotted_path = tuple(path_node.children[:-1]) + if not dotted_path: + name ,= path_node.children + raise GrammarError("Nothing was imported from grammar `%s`" % name) + name = path_node.children[-1] # Get name from dotted path + aliases = {name.value: (arg1 or name).value} # Aliases if exist + + if path_node.data == 'import_lib': # Import from library + base_path = None + else: # Relative import + if grammar_name == '': # Import relative to script file path if grammar is coded in script + try: + base_file = os.path.abspath(sys.modules['__main__'].__file__) + except AttributeError: + base_file = None + else: + base_file = grammar_name # Import relative to grammar file path if external grammar file + if base_file: + if isinstance(base_file, PackageResource): + base_path = PackageResource(base_file.pkg_name, os.path.split(base_file.path)[0]) + else: + base_path = os.path.split(base_file)[0] + else: + base_path = os.path.abspath(os.path.curdir) + + return dotted_path, base_path, aliases - name = '__IGNORE_%d'% len(ignore_names) - ignore_names.append(name) - term_defs.append((name, (t, 1))) + def _unpack_definition(self, tree, mangle): - # Verify correctness 2 - terminal_names = set() - for name, _ in term_defs: - if name in terminal_names: - raise GrammarError("Terminal '%s' defined more than once" % name) - terminal_names.add(name) + if tree.data == 'rule': + name, params, exp, opts = _make_rule_tuple(*tree.children) + is_term = False + else: + name = tree.children[0].value + params = () # TODO terminal templates + opts = int(tree.children[1]) if len(tree.children) == 3 else TOKEN_DEFAULT_PRIORITY # priority + exp = tree.children[-1] + is_term = True + + if mangle is not None: + params = tuple(mangle(p) for p in params) + name = mangle(name) + + exp = _mangle_definition_tree(exp, mangle) + return name, is_term, exp, params, opts - if set(ignore_names) > terminal_names: - raise GrammarError("Terminals %s were marked to ignore but were not defined!" % (set(ignore_names) - terminal_names)) + def load_grammar(self, grammar_text: str, grammar_name: str="", mangle: Optional[Callable[[str], str]]=None) -> None: + tree = _parse_grammar(grammar_text, grammar_name) + + imports: Dict[Tuple[str, ...], Tuple[Optional[str], Dict[str, str]]] = {} + + for stmt in tree.children: + if stmt.data == 'import': + dotted_path, base_path, aliases = self._unpack_import(stmt, grammar_name) + try: + import_base_path, import_aliases = imports[dotted_path] + assert base_path == import_base_path, 'Inconsistent base_path for %s.' % '.'.join(dotted_path) + import_aliases.update(aliases) + except KeyError: + imports[dotted_path] = base_path, aliases + + for dotted_path, (base_path, aliases) in imports.items(): + self.do_import(dotted_path, base_path, aliases, mangle) + + for stmt in tree.children: + if stmt.data in ('term', 'rule'): + self._define(*self._unpack_definition(stmt, mangle)) + elif stmt.data == 'override': + r ,= stmt.children + self._define(*self._unpack_definition(r, mangle), override=True) + elif stmt.data == 'extend': + r ,= stmt.children + self._extend(*self._unpack_definition(r, mangle)) + elif stmt.data == 'ignore': + # if mangle is not None, we shouldn't apply ignore, since we aren't in a toplevel grammar + if mangle is None: + self._ignore(*stmt.children) + elif stmt.data == 'declare': + for symbol in stmt.children: + assert isinstance(symbol, Symbol), symbol + is_term = isinstance(symbol, Terminal) + if mangle is None: + name = symbol.name + else: + name = mangle(symbol.name) + self._define(name, is_term, None) + elif stmt.data == 'import': + pass + else: + assert False, stmt + + + term_defs = { name: d.tree + for name, d in self._definitions.items() + if d.is_term + } resolve_term_references(term_defs) - rules = rule_defs - rule_names = {} - for name, params, _x, _o in rules: - if name.startswith('__'): - raise GrammarError('Names starting with double-underscore are reserved (Error at %s)' % name) - if name in rule_names: - raise GrammarError("Rule '%s' defined more than once" % name) - rule_names[name] = len(params) + def _remove_unused(self, used): + def rule_dependencies(symbol): + try: + d = self._definitions[symbol] + except KeyError: + return [] + if d.is_term: + return [] + return _find_used_symbols(d.tree) - set(d.params) + + _used = set(bfs(used, rule_dependencies)) + self._definitions = {k: v for k, v in self._definitions.items() if k in _used} + + + def do_import(self, dotted_path: Tuple[str, ...], base_path: Optional[str], aliases: Dict[str, str], base_mangle: Optional[Callable[[str], str]]=None) -> None: + assert dotted_path + mangle = _get_mangle('__'.join(dotted_path), aliases, base_mangle) + grammar_path = os.path.join(*dotted_path) + EXT + to_try = self.import_paths + ([base_path] if base_path is not None else []) + [stdlib_loader] + for source in to_try: + try: + if callable(source): + joined_path, text = source(base_path, grammar_path) + else: + joined_path = os.path.join(source, grammar_path) + with open(joined_path, encoding='utf8') as f: + text = f.read() + except IOError: + continue + else: + h = md5_digest(text) + if self.used_files.get(joined_path, h) != h: + raise RuntimeError("Grammar file was changed during importing") + self.used_files[joined_path] = h + + gb = GrammarBuilder(self.global_keep_all_tokens, self.import_paths, self.used_files) + gb.load_grammar(text, joined_path, mangle) + gb._remove_unused(map(mangle, aliases)) + for name in gb._definitions: + if name in self._definitions: + raise GrammarError("Cannot import '%s' from '%s': Symbol already defined." % (name, grammar_path)) + + self._definitions.update(**gb._definitions) + break + else: + # Search failed. Make Python throw a nice error. + open(grammar_path, encoding='utf8') + assert False, "Couldn't import grammar %s, but a corresponding file was found at a place where lark doesn't search for it" % (dotted_path,) + + + def validate(self) -> None: + for name, d in self._definitions.items(): + params = d.params + exp = d.tree - for name, params , expansions, _o in rules: for i, p in enumerate(params): - if p in rule_names: + if p in self._definitions: raise GrammarError("Template Parameter conflicts with rule %s (in template %s)" % (p, name)) if p in params[:i]: raise GrammarError("Duplicate Template Parameter %s (in template %s)" % (p, name)) - for temp in expansions.find_data('template_usage'): - sym = temp.children[0] + + if exp is None: # Remaining checks don't apply to abstract rules/terminals (created with %declare) + continue + + for temp in exp.find_data('template_usage'): + sym = temp.children[0].name args = temp.children[1:] if sym not in params: - if sym not in rule_names: - raise GrammarError("Template '%s' used but not defined (in rule %s)" % (sym, name)) - if len(args) != rule_names[sym]: - raise GrammarError("Wrong number of template arguments used for %s " - "(expected %s, got %s) (in rule %s)"%(sym, rule_names[sym], len(args), name)) - for sym in _find_used_symbols(expansions): - if sym.type == 'TERMINAL': - if sym not in terminal_names: - raise GrammarError("Token '%s' used but not defined (in rule %s)" % (sym, name)) - else: - if sym not in rule_names and sym not in params: - raise GrammarError("Rule '%s' used but not defined (in rule %s)" % (sym, name)) + if sym not in self._definitions: + self._grammar_error(d.is_term, "Template '%s' used but not defined (in {type} {name})" % sym, name) + if len(args) != len(self._definitions[sym].params): + expected, actual = len(self._definitions[sym].params), len(args) + self._grammar_error(d.is_term, "Wrong number of template arguments used for {name} " + "(expected %s, got %s) (in {type2} {name2})" % (expected, actual), sym, name) + + for sym in _find_used_symbols(exp): + if sym not in self._definitions and sym not in params: + self._grammar_error(d.is_term, "{Type} '{name}' used but not defined (in {type2} {name2})", sym, name) + + if not set(self._definitions).issuperset(self._ignore_names): + raise GrammarError("Terminals %s were marked to ignore but were not defined!" % (set(self._ignore_names) - set(self._definitions))) + + def build(self) -> Grammar: + self.validate() + rule_defs = [] + term_defs = [] + for name, d in self._definitions.items(): + (params, exp, options) = d.params, d.tree, d.options + if d.is_term: + assert len(params) == 0 + term_defs.append((name, (exp, options))) + else: + rule_defs.append((name, params, exp, options)) + # resolve_term_references(term_defs) + return Grammar(rule_defs, term_defs, self._ignore_names) + + +def verify_used_files(file_hashes): + for path, old in file_hashes.items(): + text = None + if isinstance(path, str) and os.path.exists(path): + with open(path, encoding='utf8') as f: + text = f.read() + elif isinstance(path, PackageResource): + with suppress(IOError): + text = pkgutil.get_data(*path).decode('utf-8') + if text is None: # We don't know how to load the path. ignore it. + continue + + current = md5_digest(text) + if old != current: + logger.info("File %r changed, rebuilding Parser" % path) + return False + return True +def list_grammar_imports(grammar, import_paths=[]): + "Returns a list of paths to the lark grammars imported by the given grammar (recursively)" + builder = GrammarBuilder(False, import_paths) + builder.load_grammar(grammar, '') + return list(builder.used_files.keys()) - return Grammar(rules, term_defs, ignore_names) +def load_grammar(grammar, source, import_paths, global_keep_all_tokens): + builder = GrammarBuilder(global_keep_all_tokens, import_paths) + builder.load_grammar(grammar, source) + return builder.build(), builder.used_files +def md5_digest(s: str) -> str: + """Get the md5 digest of a string -def load_grammar(grammar, source, re_): - return GrammarLoader(re_).load_grammar(grammar, source) + Supports the `usedforsecurity` argument for Python 3.9+ to allow running on + a FIPS-enabled system. + """ + if sys.version_info >= (3, 9): + return hashlib.md5(s.encode('utf8'), usedforsecurity=False).hexdigest() + else: + return hashlib.md5(s.encode('utf8')).hexdigest() diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parse_tree_builder.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parse_tree_builder.py index 5a7c5d703..a6003a92e 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parse_tree_builder.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parse_tree_builder.py @@ -1,7 +1,8 @@ -from .exceptions import GrammarError +from typing import List + +from .exceptions import GrammarError, ConfigurationError from .lexer import Token from .tree import Tree -from .visitors import InlineTransformer # XXX Deprecated from .visitors import Transformer_InPlace from .visitors import _vargs_meta, _vargs_meta_inline @@ -20,50 +21,71 @@ def __call__(self, children): else: return self.node_builder(children) + + class PropagatePositions: - def __init__(self, node_builder): + def __init__(self, node_builder, node_filter=None): self.node_builder = node_builder + self.node_filter = node_filter def __call__(self, children): res = self.node_builder(children) - # local reference to Tree.meta reduces number of presence checks if isinstance(res, Tree): + # Calculate positions while the tree is streaming, according to the rule: + # - nodes start at the start of their first child's container, + # and end at the end of their last child's container. + # Containers are nodes that take up space in text, but have been inlined in the tree. + res_meta = res.meta - for c in children: - if isinstance(c, Tree): - child_meta = c.meta - if not child_meta.empty: - res_meta.line = child_meta.line - res_meta.column = child_meta.column - res_meta.start_pos = child_meta.start_pos - res_meta.empty = False - break - elif isinstance(c, Token): - res_meta.line = c.line - res_meta.column = c.column - res_meta.start_pos = c.pos_in_stream + + first_meta = self._pp_get_meta(children) + if first_meta is not None: + if not hasattr(res_meta, 'line'): + # meta was already set, probably because the rule has been inlined (e.g. `?rule`) + res_meta.line = getattr(first_meta, 'container_line', first_meta.line) + res_meta.column = getattr(first_meta, 'container_column', first_meta.column) + res_meta.start_pos = getattr(first_meta, 'container_start_pos', first_meta.start_pos) res_meta.empty = False - break - - for c in reversed(children): - if isinstance(c, Tree): - child_meta = c.meta - if not child_meta.empty: - res_meta.end_line = child_meta.end_line - res_meta.end_column = child_meta.end_column - res_meta.end_pos = child_meta.end_pos - res_meta.empty = False - break - elif isinstance(c, Token): - res_meta.end_line = c.end_line - res_meta.end_column = c.end_column - res_meta.end_pos = c.end_pos + + res_meta.container_line = getattr(first_meta, 'container_line', first_meta.line) + res_meta.container_column = getattr(first_meta, 'container_column', first_meta.column) + + last_meta = self._pp_get_meta(reversed(children)) + if last_meta is not None: + if not hasattr(res_meta, 'end_line'): + res_meta.end_line = getattr(last_meta, 'container_end_line', last_meta.end_line) + res_meta.end_column = getattr(last_meta, 'container_end_column', last_meta.end_column) + res_meta.end_pos = getattr(last_meta, 'container_end_pos', last_meta.end_pos) res_meta.empty = False - break + + res_meta.container_end_line = getattr(last_meta, 'container_end_line', last_meta.end_line) + res_meta.container_end_column = getattr(last_meta, 'container_end_column', last_meta.end_column) return res + def _pp_get_meta(self, children): + for c in children: + if self.node_filter is not None and not self.node_filter(c): + continue + if isinstance(c, Tree): + if not c.meta.empty: + return c.meta + elif isinstance(c, Token): + return c + elif hasattr(c, '__lark_meta__'): + return c.__lark_meta__() + +def make_propagate_positions(option): + if callable(option): + return partial(PropagatePositions, node_filter=option) + elif option is True: + return PropagatePositions + elif option is False: + return None + + raise ConfigurationError('Invalid option for propagate_positions: %r' % option) + class ChildFilter: def __init__(self, to_include, append_none, node_builder): @@ -87,8 +109,9 @@ def __call__(self, children): return self.node_builder(filtered) + class ChildFilterLALR(ChildFilter): - "Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)" + """Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)""" def __call__(self, children): filtered = [] @@ -108,6 +131,7 @@ def __call__(self, children): return self.node_builder(filtered) + class ChildFilterLALR_NoPlaceholders(ChildFilter): "Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)" def __init__(self, to_include, node_builder): @@ -126,10 +150,12 @@ def __call__(self, children): filtered.append(children[i]) return self.node_builder(filtered) + def _should_expand(sym): return not sym.is_term and sym.name.startswith('_') -def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous, _empty_indices): + +def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous, _empty_indices: List[bool]): # Prepare empty_indices as: How many Nones to insert at each index? if _empty_indices: assert _empty_indices.count(False) == len(expansion) @@ -156,21 +182,22 @@ def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous, _empty_indi # LALR without placeholders return partial(ChildFilterLALR_NoPlaceholders, [(i, x) for i,x,_ in to_include]) + class AmbiguousExpander: """Deal with the case where we're expanding children ('_rule') into a parent but the children are ambiguous. i.e. (parent->_ambig->_expand_this_rule). In this case, make the parent itself ambiguous with as many copies as their are ambiguous children, and then copy the ambiguous children - into the right parents in the right places, essentially shifting the ambiguiuty up the tree.""" + into the right parents in the right places, essentially shifting the ambiguity up the tree.""" def __init__(self, to_expand, tree_class, node_builder): self.node_builder = node_builder self.tree_class = tree_class self.to_expand = to_expand def __call__(self, children): - def _is_ambig_tree(child): - return hasattr(child, 'data') and child.data == '_ambig' + def _is_ambig_tree(t): + return hasattr(t, 'data') and t.data == '_ambig' - #### When we're repeatedly expanding ambiguities we can end up with nested ambiguities. + # -- When we're repeatedly expanding ambiguities we can end up with nested ambiguities. # All children of an _ambig node should be a derivation of that ambig node, hence # it is safe to assume that if we see an _ambig node nested within an ambig node # it is safe to simply expand it into the parent _ambig node as an alternative derivation. @@ -180,26 +207,103 @@ def _is_ambig_tree(child): if i in self.to_expand: ambiguous.append(i) - to_expand = [j for j, grandchild in enumerate(child.children) if _is_ambig_tree(grandchild)] - child.expand_kids_by_index(*to_expand) + child.expand_kids_by_data('_ambig') if not ambiguous: return self.node_builder(children) - expand = [ iter(child.children) if i in ambiguous else repeat(child) for i, child in enumerate(children) ] + expand = [iter(child.children) if i in ambiguous else repeat(child) for i, child in enumerate(children)] return self.tree_class('_ambig', [self.node_builder(list(f[0])) for f in product(zip(*expand))]) + def maybe_create_ambiguous_expander(tree_class, expansion, keep_all_tokens): to_expand = [i for i, sym in enumerate(expansion) if keep_all_tokens or ((not (sym.is_term and sym.filter_out)) and _should_expand(sym))] if to_expand: return partial(AmbiguousExpander, to_expand, tree_class) -def ptb_inline_args(func): - @wraps(func) - def f(children): - return func(*children) - return f + +class AmbiguousIntermediateExpander: + """ + Propagate ambiguous intermediate nodes and their derivations up to the + current rule. + + In general, converts + + rule + _iambig + _inter + someChildren1 + ... + _inter + someChildren2 + ... + someChildren3 + ... + + to + + _ambig + rule + someChildren1 + ... + someChildren3 + ... + rule + someChildren2 + ... + someChildren3 + ... + rule + childrenFromNestedIambigs + ... + someChildren3 + ... + ... + + propagating up any nested '_iambig' nodes along the way. + """ + + def __init__(self, tree_class, node_builder): + self.node_builder = node_builder + self.tree_class = tree_class + + def __call__(self, children): + def _is_iambig_tree(child): + return hasattr(child, 'data') and child.data == '_iambig' + + def _collapse_iambig(children): + """ + Recursively flatten the derivations of the parent of an '_iambig' + node. Returns a list of '_inter' nodes guaranteed not + to contain any nested '_iambig' nodes, or None if children does + not contain an '_iambig' node. + """ + + # Due to the structure of the SPPF, + # an '_iambig' node can only appear as the first child + if children and _is_iambig_tree(children[0]): + iambig_node = children[0] + result = [] + for grandchild in iambig_node.children: + collapsed = _collapse_iambig(grandchild.children) + if collapsed: + for child in collapsed: + child.children += children[1:] + result += collapsed + else: + new_tree = self.tree_class('_inter', grandchild.children + children[1:]) + result.append(new_tree) + return result + + collapsed = _collapse_iambig(children) + if collapsed: + processed_nodes = [self.node_builder(c.children) for c in collapsed] + return self.tree_class('_ambig', processed_nodes) + + return self.node_builder(children) + + def inplace_transformer(func): @wraps(func) @@ -209,9 +313,11 @@ def f(children): return func(tree) return f + def apply_visit_wrapper(func, name, wrapper): if wrapper is _vargs_meta or wrapper is _vargs_meta_inline: raise NotImplementedError("Meta args not supported for internal transformer") + @wraps(func) def f(children): return wrapper(func, name, children, None) @@ -219,50 +325,54 @@ def f(children): class ParseTreeBuilder: - def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False, maybe_placeholders=False): + def __init__(self, rules, tree_class, propagate_positions=False, ambiguous=False, maybe_placeholders=False): self.tree_class = tree_class self.propagate_positions = propagate_positions - self.always_keep_all_tokens = keep_all_tokens self.ambiguous = ambiguous self.maybe_placeholders = maybe_placeholders self.rule_builders = list(self._init_builders(rules)) def _init_builders(self, rules): + propagate_positions = make_propagate_positions(self.propagate_positions) + for rule in rules: options = rule.options - keep_all_tokens = self.always_keep_all_tokens or options.keep_all_tokens + keep_all_tokens = options.keep_all_tokens expand_single_child = options.expand1 wrapper_chain = list(filter(None, [ (expand_single_child and not rule.alias) and ExpandSingleChild, maybe_create_child_filter(rule.expansion, keep_all_tokens, self.ambiguous, options.empty_indices if self.maybe_placeholders else None), - self.propagate_positions and PropagatePositions, + propagate_positions, self.ambiguous and maybe_create_ambiguous_expander(self.tree_class, rule.expansion, keep_all_tokens), + self.ambiguous and partial(AmbiguousIntermediateExpander, self.tree_class) ])) yield rule, wrapper_chain - def create_callback(self, transformer=None): callbacks = {} + default_handler = getattr(transformer, '__default__', None) + if default_handler: + def default_callback(data, children): + return default_handler(data, children, None) + else: + default_callback = self.tree_class + for rule, wrapper_chain in self.rule_builders: user_callback_name = rule.alias or rule.options.template_source or rule.origin.name try: f = getattr(transformer, user_callback_name) - # XXX InlineTransformer is deprecated! wrapper = getattr(f, 'visit_wrapper', None) if wrapper is not None: f = apply_visit_wrapper(f, user_callback_name, wrapper) - else: - if isinstance(transformer, InlineTransformer): - f = ptb_inline_args(f) - elif isinstance(transformer, Transformer_InPlace): - f = inplace_transformer(f) + elif isinstance(transformer, Transformer_InPlace): + f = inplace_transformer(f) except AttributeError: - f = partial(self.tree_class, user_callback_name) + f = partial(default_callback, user_callback_name) for w in wrapper_chain: f = w(f) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parser_frontends.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parser_frontends.py index c453ab67a..4e28e3613 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parser_frontends.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parser_frontends.py @@ -1,225 +1,220 @@ -from functools import partial +from typing import Any, Callable, Dict, Tuple +from .exceptions import ConfigurationError, GrammarError, assert_config from .utils import get_regexp_width, Serialize from .parsers.grammar_analysis import GrammarAnalyzer -from .lexer import TraditionalLexer, ContextualLexer, Lexer, Token +from .lexer import LexerThread, BasicLexer, ContextualLexer, Lexer from .parsers import earley, xearley, cyk from .parsers.lalr_parser import LALR_Parser -from .grammar import Rule from .tree import Tree -from .common import LexerConf +from .common import LexerConf, ParserConf, _ParserArgType, _LexerArgType ###{standalone -def get_frontend(parser, lexer): - if parser=='lalr': - if lexer is None: - raise ValueError('The LALR parser requires use of a lexer') - elif lexer == 'standard': - return LALR_TraditionalLexer - elif lexer == 'contextual': - return LALR_ContextualLexer - elif issubclass(lexer, Lexer): - return partial(LALR_CustomLexer, lexer) - else: - raise ValueError('Unknown lexer: %s' % lexer) - elif parser=='earley': - if lexer=='standard': - return Earley - elif lexer=='dynamic': - return XEarley - elif lexer=='dynamic_complete': - return XEarley_CompleteLex - elif lexer=='contextual': - raise ValueError('The Earley parser does not support the contextual parser') - else: - raise ValueError('Unknown lexer: %s' % lexer) - elif parser == 'cyk': - if lexer == 'standard': - return CYK - else: - raise ValueError('CYK parser requires using standard parser.') +def _wrap_lexer(lexer_class): + future_interface = getattr(lexer_class, '__future_interface__', False) + if future_interface: + return lexer_class else: - raise ValueError('Unknown parser: %s' % parser) + class CustomLexerWrapper(Lexer): + def __init__(self, lexer_conf): + self.lexer = lexer_class(lexer_conf) + def lex(self, lexer_state, parser_state): + return self.lexer.lex(lexer_state.text) + return CustomLexerWrapper -class _ParserFrontend(Serialize): - def _parse(self, input, start, *args): - if start is None: - start = self.start - if len(start) > 1: - raise ValueError("Lark initialized with more than 1 possible start rule. Must specify which start rule to parse", start) - start ,= start - return self.parser.parse(input, start, *args) +def _deserialize_parsing_frontend(data, memo, lexer_conf, callbacks, options): + parser_conf = ParserConf.deserialize(data['parser_conf'], memo) + cls = (options and options._plugins.get('LALR_Parser')) or LALR_Parser + parser = cls.deserialize(data['parser'], memo, callbacks, options.debug) + parser_conf.callbacks = callbacks + return ParsingFrontend(lexer_conf, parser_conf, options, parser=parser) -class WithLexer(_ParserFrontend): - lexer = None - parser = None - lexer_conf = None - start = None +_parser_creators: 'Dict[str, Callable[[LexerConf, Any, Any], Any]]' = {} - __serialize_fields__ = 'parser', 'lexer_conf', 'start' - __serialize_namespace__ = LexerConf, - def __init__(self, lexer_conf, parser_conf, re_, options=None): +class ParsingFrontend(Serialize): + __serialize_fields__ = 'lexer_conf', 'parser_conf', 'parser' + + def __init__(self, lexer_conf, parser_conf, options, parser=None): + self.parser_conf = parser_conf self.lexer_conf = lexer_conf - self.start = parser_conf.start - self.postlex = lexer_conf.postlex - self.re = re_ - - @classmethod - def deserialize(cls, data, memo, callbacks, postlex, re_): - inst = super(WithLexer, cls).deserialize(data, memo) - inst.re = re_ - inst.postlex = postlex - inst.parser = LALR_Parser.deserialize(inst.parser, memo, callbacks) - inst.init_lexer() - return inst - - def _serialize(self, data, memo): - data['parser'] = data['parser'].serialize(memo) - - def lex(self, *args): - stream = self.lexer.lex(*args) - return self.postlex.process(stream) if self.postlex else stream - - def parse(self, text, start=None): - token_stream = self.lex(text) - return self._parse(token_stream, start) - - def init_traditional_lexer(self): - self.lexer = TraditionalLexer(self.lexer_conf.tokens, re_=self.re, ignore=self.lexer_conf.ignore, user_callbacks=self.lexer_conf.callbacks, g_regex_flags=self.lexer_conf.g_regex_flags) - -class LALR_WithLexer(WithLexer): - def __init__(self, lexer_conf, parser_conf, re_, options=None): - debug = options.debug if options else False - self.re = re_ - self.parser = LALR_Parser(parser_conf, debug=debug) - WithLexer.__init__(self, lexer_conf, parser_conf, re_, options) - - self.init_lexer() - - def init_lexer(self): - raise NotImplementedError() - -class LALR_TraditionalLexer(LALR_WithLexer): - def init_lexer(self): - self.init_traditional_lexer() - -class LALR_ContextualLexer(LALR_WithLexer): - def init_lexer(self): - states = {idx:list(t.keys()) for idx, t in self.parser._parse_table.states.items()} - always_accept = self.postlex.always_accept if self.postlex else () - self.lexer = ContextualLexer(self.lexer_conf.tokens, states, - re_=self.re, - ignore=self.lexer_conf.ignore, - always_accept=always_accept, - user_callbacks=self.lexer_conf.callbacks, - g_regex_flags=self.lexer_conf.g_regex_flags) - - - def parse(self, text, start=None): - parser_state = [None] - def set_parser_state(s): - parser_state[0] = s - - token_stream = self.lex(text, lambda: parser_state[0]) - return self._parse(token_stream, start, set_parser_state) -###} + self.options = options -class LALR_CustomLexer(LALR_WithLexer): - def __init__(self, lexer_cls, lexer_conf, parser_conf, re_, options=None): - self.lexer = lexer_cls(lexer_conf, re_=re_) - debug = options.debug if options else False - self.parser = LALR_Parser(parser_conf, debug=debug) - WithLexer.__init__(self, lexer_conf, parser_conf, re_, options) - - -def tokenize_text(text): - line = 1 - col_start_pos = 0 - for i, ch in enumerate(text): - if '\n' in ch: - line += ch.count('\n') - col_start_pos = i + ch.rindex('\n') - yield Token('CHAR', ch, line=line, column=i - col_start_pos) - -class Earley(WithLexer): - def __init__(self, lexer_conf, parser_conf, re_, options=None): - WithLexer.__init__(self, lexer_conf, parser_conf, re_, options) - self.init_traditional_lexer() - - resolve_ambiguity = options.ambiguity == 'resolve' - debug = options.debug if options else False - self.parser = earley.Parser(parser_conf, self.match, resolve_ambiguity=resolve_ambiguity, debug=debug) - - def match(self, term, token): - return term.name == token.type - - -class XEarley(_ParserFrontend): - def __init__(self, lexer_conf, parser_conf, re_, options=None, **kw): - self.re = re_ - - self.token_by_name = {t.name:t for t in lexer_conf.tokens} - self.start = parser_conf.start - - self._prepare_match(lexer_conf) - resolve_ambiguity = options.ambiguity == 'resolve' - debug = options.debug if options else False - self.parser = xearley.Parser(parser_conf, - self.match, - ignore=lexer_conf.ignore, - resolve_ambiguity=resolve_ambiguity, - debug=debug, - **kw - ) + # Set-up parser + if parser: # From cache + self.parser = parser + else: + create_parser = _parser_creators.get(parser_conf.parser_type) + assert create_parser is not None, "{} is not supported in standalone mode".format( + parser_conf.parser_type + ) + self.parser = create_parser(lexer_conf, parser_conf, options) + + # Set-up lexer + lexer_type = lexer_conf.lexer_type + self.skip_lexer = False + if lexer_type in ('dynamic', 'dynamic_complete'): + assert lexer_conf.postlex is None + self.skip_lexer = True + return + + try: + create_lexer = { + 'basic': create_basic_lexer, + 'contextual': create_contextual_lexer, + }[lexer_type] + except KeyError: + assert issubclass(lexer_type, Lexer), lexer_type + self.lexer = _wrap_lexer(lexer_type)(lexer_conf) + else: + self.lexer = create_lexer(lexer_conf, self.parser, lexer_conf.postlex, options) - def match(self, term, text, index=0): - return self.regexps[term.name].match(text, index) + if lexer_conf.postlex: + self.lexer = PostLexConnector(self.lexer, lexer_conf.postlex) - def _prepare_match(self, lexer_conf): + def _verify_start(self, start=None): + if start is None: + start_decls = self.parser_conf.start + if len(start_decls) > 1: + raise ConfigurationError("Lark initialized with more than 1 possible start rule. Must specify which start rule to parse", start_decls) + start ,= start_decls + elif start not in self.parser_conf.start: + raise ConfigurationError("Unknown start rule %s. Must be one of %r" % (start, self.parser_conf.start)) + return start + + def _make_lexer_thread(self, text): + cls = (self.options and self.options._plugins.get('LexerThread')) or LexerThread + return text if self.skip_lexer else cls.from_text(self.lexer, text) + + def parse(self, text, start=None, on_error=None): + chosen_start = self._verify_start(start) + kw = {} if on_error is None else {'on_error': on_error} + stream = self._make_lexer_thread(text) + return self.parser.parse(stream, chosen_start, **kw) + + def parse_interactive(self, text=None, start=None): + chosen_start = self._verify_start(start) + if self.parser_conf.parser_type != 'lalr': + raise ConfigurationError("parse_interactive() currently only works with parser='lalr' ") + stream = self._make_lexer_thread(text) + return self.parser.parse_interactive(stream, chosen_start) + + +def _validate_frontend_args(parser, lexer) -> None: + assert_config(parser, ('lalr', 'earley', 'cyk')) + if not isinstance(lexer, type): # not custom lexer? + expected = { + 'lalr': ('basic', 'contextual'), + 'earley': ('basic', 'dynamic', 'dynamic_complete'), + 'cyk': ('basic', ), + }[parser] + assert_config(lexer, expected, 'Parser %r does not support lexer %%r, expected one of %%s' % parser) + + +def _get_lexer_callbacks(transformer, terminals): + result = {} + for terminal in terminals: + callback = getattr(transformer, terminal.name, None) + if callback is not None: + result[terminal.name] = callback + return result + +class PostLexConnector: + def __init__(self, lexer, postlexer): + self.lexer = lexer + self.postlexer = postlexer + + def lex(self, lexer_state, parser_state): + i = self.lexer.lex(lexer_state, parser_state) + return self.postlexer.process(i) + + + +def create_basic_lexer(lexer_conf, parser, postlex, options): + cls = (options and options._plugins.get('BasicLexer')) or BasicLexer + return cls(lexer_conf) + +def create_contextual_lexer(lexer_conf, parser, postlex, options): + cls = (options and options._plugins.get('ContextualLexer')) or ContextualLexer + states = {idx:list(t.keys()) for idx, t in parser._parse_table.states.items()} + always_accept = postlex.always_accept if postlex else () + return cls(lexer_conf, states, always_accept=always_accept) + +def create_lalr_parser(lexer_conf, parser_conf, options=None): + debug = options.debug if options else False + cls = (options and options._plugins.get('LALR_Parser')) or LALR_Parser + return cls(parser_conf, debug=debug) + +_parser_creators['lalr'] = create_lalr_parser + +###} + +class EarleyRegexpMatcher: + def __init__(self, lexer_conf): self.regexps = {} - for t in lexer_conf.tokens: - if t.priority != 1: - raise ValueError("Dynamic Earley doesn't support weights on terminals", t, t.priority) + for t in lexer_conf.terminals: regexp = t.pattern.to_regexp() try: width = get_regexp_width(regexp)[0] except ValueError: - raise ValueError("Bad regexp in token %s: %s" % (t.name, regexp)) + raise GrammarError("Bad regexp in token %s: %s" % (t.name, regexp)) else: if width == 0: - raise ValueError("Dynamic Earley doesn't allow zero-width regexps", t) + raise GrammarError("Dynamic Earley doesn't allow zero-width regexps", t) + if lexer_conf.use_bytes: + regexp = regexp.encode('utf-8') + + self.regexps[t.name] = lexer_conf.re_module.compile(regexp, lexer_conf.g_regex_flags) + + def match(self, term, text, index=0): + return self.regexps[term.name].match(text, index) + - self.regexps[t.name] = self.re.compile(regexp, lexer_conf.g_regex_flags) +def create_earley_parser__dynamic(lexer_conf, parser_conf, options=None, **kw): + if lexer_conf.callbacks: + raise GrammarError("Earley's dynamic lexer doesn't support lexer_callbacks.") - def parse(self, text, start): - return self._parse(text, start) + earley_matcher = EarleyRegexpMatcher(lexer_conf) + return xearley.Parser(lexer_conf, parser_conf, earley_matcher.match, **kw) -class XEarley_CompleteLex(XEarley): - def __init__(self, *args, **kw): - XEarley.__init__(self, *args, complete_lex=True, **kw) +def _match_earley_basic(term, token): + return term.name == token.type +def create_earley_parser__basic(lexer_conf, parser_conf, options, **kw): + return earley.Parser(lexer_conf, parser_conf, _match_earley_basic, **kw) +def create_earley_parser(lexer_conf, parser_conf, options): + resolve_ambiguity = options.ambiguity == 'resolve' + debug = options.debug if options else False + tree_class = options.tree_class or Tree if options.ambiguity != 'forest' else None + + extra = {} + if lexer_conf.lexer_type == 'dynamic': + f = create_earley_parser__dynamic + elif lexer_conf.lexer_type == 'dynamic_complete': + extra['complete_lex'] =True + f = create_earley_parser__dynamic + else: + f = create_earley_parser__basic + + return f(lexer_conf, parser_conf, options, resolve_ambiguity=resolve_ambiguity, debug=debug, tree_class=tree_class, **extra) -class CYK(WithLexer): - def __init__(self, lexer_conf, parser_conf, re_, options=None): - WithLexer.__init__(self, lexer_conf, parser_conf, re_, options) - self.init_traditional_lexer() +class CYK_FrontEnd: + def __init__(self, lexer_conf, parser_conf, options=None): self._analysis = GrammarAnalyzer(parser_conf) self.parser = cyk.Parser(parser_conf.rules) self.callbacks = parser_conf.callbacks - def parse(self, text, start): - tokens = list(self.lex(text)) - parse = self._parse(tokens, start) - parse = self._transform(parse) - return parse + def parse(self, lexer_thread, start): + tokens = list(lexer_thread.lex(None)) + tree = self.parser.parse(tokens, start) + return self._transform(tree) def _transform(self, tree): subtrees = list(tree.iter_subtrees()) @@ -231,3 +226,20 @@ def _transform(self, tree): def _apply_callback(self, tree): return self.callbacks[tree.rule](tree.children) + +_parser_creators['earley'] = create_earley_parser +_parser_creators['cyk'] = CYK_FrontEnd + + +def _construct_parsing_frontend( + parser_type: _ParserArgType, + lexer_type: _LexerArgType, + lexer_conf, + parser_conf, + options +): + assert isinstance(lexer_conf, LexerConf) + assert isinstance(parser_conf, ParserConf) + parser_conf.parser_type = parser_type + lexer_conf.lexer_type = lexer_type + return ParsingFrontend(lexer_conf, parser_conf, options) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/cyk.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/cyk.py index ff0924f24..82818ccf9 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/cyk.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/cyk.py @@ -23,7 +23,7 @@ def match(t, s): return t.name == s.type -class Rule(object): +class Rule: """Context-free grammar rule.""" def __init__(self, lhs, rhs, weight, alias): @@ -51,7 +51,7 @@ def __ne__(self, other): return not (self == other) -class Grammar(object): +class Grammar: """Context-free grammar.""" def __init__(self, rules): @@ -68,7 +68,7 @@ def __repr__(self): # Parse tree data structures -class RuleNode(object): +class RuleNode: """A node in the parse tree, which also contains the full rhs rule.""" def __init__(self, rule, children, weight=0): @@ -81,7 +81,7 @@ def __repr__(self): -class Parser(object): +class Parser: """Parser wrapper.""" def __init__(self, rules): @@ -186,7 +186,7 @@ def _parse(s, g): # * Empty rules (epsilon rules) -class CnfWrapper(object): +class CnfWrapper: """CNF wrapper for grammar. Validates that the input grammar is CNF and provides helper data structures. diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley.py index 59e9a06a7..2a047b032 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley.py @@ -1,4 +1,4 @@ -"""This module implements an scanerless Earley parser. +"""This module implements an Earley parser. The core Earley algorithm used here is based on Elizabeth Scott's implementation, here: https://www.sciencedirect.com/science/article/pii/S1571066108001497 @@ -6,26 +6,28 @@ That is probably the best reference for understanding the algorithm here. The Earley parser outputs an SPPF-tree as per that document. The SPPF tree format -is better documented here: - http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/ +is explained here: https://lark-parser.readthedocs.io/en/latest/_static/sppf/sppf.html """ -import logging from collections import deque -from ..visitors import Transformer_InPlace, v_args +from ..lexer import Token +from ..tree import Tree from ..exceptions import UnexpectedEOF, UnexpectedToken +from ..utils import logger from .grammar_analysis import GrammarAnalyzer from ..grammar import NonTerminal -from .earley_common import Item, TransitiveItem -from .earley_forest import ForestToTreeVisitor, ForestSumVisitor, SymbolNode, ForestToAmbiguousTreeVisitor +from .earley_common import Item +from .earley_forest import ForestSumVisitor, SymbolNode, TokenNode, ForestToParseTree class Parser: - def __init__(self, parser_conf, term_matcher, resolve_ambiguity=True, debug=False): + def __init__(self, lexer_conf, parser_conf, term_matcher, resolve_ambiguity=True, debug=False, tree_class=Tree): analysis = GrammarAnalyzer(parser_conf) + self.lexer_conf = lexer_conf self.parser_conf = parser_conf self.resolve_ambiguity = resolve_ambiguity self.debug = debug + self.tree_class = tree_class self.FIRST = analysis.FIRST self.NULLABLE = analysis.NULLABLE @@ -42,13 +44,21 @@ def __init__(self, parser_conf, term_matcher, resolve_ambiguity=True, debug=Fals if rule.origin not in self.predictions: self.predictions[rule.origin] = [x.rule for x in analysis.expand_rule(rule.origin)] - ## Detect if any rules have priorities set. If the user specified priority = "none" then - # the priorities will be stripped from all rules before they reach us, allowing us to + ## Detect if any rules/terminals have priorities set. If the user specified priority = None, then + # the priorities will be stripped from all rules/terminals before they reach us, allowing us to # skip the extra tree walk. We'll also skip this if the user just didn't specify priorities - # on any rules. + # on any rules/terminals. if self.forest_sum_visitor is None and rule.options.priority is not None: self.forest_sum_visitor = ForestSumVisitor + # Check terminals for priorities + # Ignore terminal priorities if the basic lexer is used + if self.lexer_conf.lexer_type != 'basic' and self.forest_sum_visitor is None: + for term in self.lexer_conf.terminals: + if term.priority: + self.forest_sum_visitor = ForestSumVisitor + break + self.term_matcher = term_matcher @@ -145,7 +155,7 @@ def predict_and_complete(self, i, to_scan, columns, transitives): column.add(new_item) items.append(new_item) - def _parse(self, stream, columns, to_scan, start_symbol=None): + def _parse(self, lexer, columns, to_scan, start_symbol=None): def is_quasi_complete(item): if item.is_complete: return True @@ -159,60 +169,8 @@ def is_quasi_complete(item): quasi = quasi.advance() return True - def create_leo_transitives(origin, start): - visited = set() - to_create = [] - trule = None - previous = None - - ### Recursively walk backwards through the Earley sets until we find the - # first transitive candidate. If this is done continuously, we shouldn't - # have to walk more than 1 hop. - while True: - if origin in transitives[start]: - previous = trule = transitives[start][origin] - break - - is_empty_rule = not self.FIRST[origin] - if is_empty_rule: - break - - candidates = [ candidate for candidate in columns[start] if candidate.expect is not None and origin == candidate.expect ] - if len(candidates) != 1: - break - originator = next(iter(candidates)) - - if originator is None or originator in visited: - break - - visited.add(originator) - if not is_quasi_complete(originator): - break - - trule = originator.advance() - if originator.start != start: - visited.clear() - - to_create.append((origin, start, originator)) - origin = originator.rule.origin - start = originator.start - - # If a suitable Transitive candidate is not found, bail. - if trule is None: - return - - #### Now walk forwards and create Transitive Items in each set we walked through; and link - # each transitive item to the next set forwards. - while to_create: - origin, start, originator = to_create.pop() - titem = None - if previous is not None: - titem = previous.next_titem = TransitiveItem(origin, trule, originator, previous.column) - else: - titem = TransitiveItem(origin, trule, originator, start) - previous = transitives[start][origin] = titem - - + # def create_leo_transitives(origin, start): + # ... # removed at commit 4c1cfb2faf24e8f8bff7112627a00b94d261b420 def scan(i, token, to_scan): """The core Earley Scanner. @@ -232,8 +190,17 @@ def scan(i, token, to_scan): if match(item.expect, token): new_item = item.advance() label = (new_item.s, new_item.start, i) + # 'terminals' may not contain token.type when using %declare + # Additionally, token is not always a Token + # For example, it can be a Tree when using TreeMatcher + term = terminals.get(token.type) if isinstance(token, Token) else None + # Set the priority of the token node to 0 so that the + # terminal priorities do not affect the Tree chosen by + # ForestSumVisitor after the basic lexer has already + # "used up" the terminal priorities + token_node = TokenNode(token, term, priority=0) new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label)) - new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token) + new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node) if new_item.expect in self.TERMINALS: # add (B ::= Aai+1.B, h, y) to Q' @@ -244,7 +211,7 @@ def scan(i, token, to_scan): if not next_set and not next_to_scan: expect = {i.expect.name for i in to_scan} - raise UnexpectedToken(token, expect, considered_rules = set(to_scan)) + raise UnexpectedToken(token, expect, considered_rules=set(to_scan), state=frozenset(i.s for i in to_scan)) return next_to_scan @@ -252,6 +219,8 @@ def scan(i, token, to_scan): # Define parser functions match = self.term_matcher + terminals = self.lexer_conf.terminals_by_name + # Cache for nodes & tokens created in a particular parse step. transitives = [{}] @@ -260,20 +229,24 @@ def scan(i, token, to_scan): # Completions will be added to the SPPF tree, and predictions will be recursively # processed down to terminals/empty nodes to be added to the scanner for the next # step. + expects = {i.expect for i in to_scan} i = 0 - for token in stream: + for token in lexer.lex(expects): self.predict_and_complete(i, to_scan, columns, transitives) to_scan = scan(i, token, to_scan) i += 1 + expects.clear() + expects |= {i.expect for i in to_scan} + self.predict_and_complete(i, to_scan, columns, transitives) ## Column is now the final column in the parse. assert i == len(columns)-1 return to_scan - def parse(self, stream, start): + def parse(self, lexer, start): assert start, start start_symbol = NonTerminal(start) @@ -290,39 +263,33 @@ def parse(self, stream, start): else: columns[0].add(item) - to_scan = self._parse(stream, columns, to_scan, start_symbol) + to_scan = self._parse(lexer, columns, to_scan, start_symbol) # If the parse was successful, the start # symbol should have been completed in the last step of the Earley cycle, and will be in # this column. Find the item for the start_symbol, which is the root of the SPPF tree. solutions = [n.node for n in columns[-1] if n.is_complete and n.node is not None and n.s == start_symbol and n.start == 0] + if not solutions: + expected_terminals = [t.expect.name for t in to_scan] + raise UnexpectedEOF(expected_terminals, state=frozenset(i.s for i in to_scan)) + if self.debug: from .earley_forest import ForestToPyDotVisitor try: debug_walker = ForestToPyDotVisitor() except ImportError: - logging.warning("Cannot find dependency 'pydot', will not generate sppf debug image") + logger.warning("Cannot find dependency 'pydot', will not generate sppf debug image") else: debug_walker.visit(solutions[0], "sppf.png") - if not solutions: - expected_tokens = [t.expect for t in to_scan] - raise UnexpectedEOF(expected_tokens) - elif len(solutions) > 1: + if len(solutions) > 1: assert False, 'Earley should not generate multiple start symbol items!' - # Perform our SPPF -> AST conversion using the right ForestVisitor. - forest_tree_visitor_cls = ForestToTreeVisitor if self.resolve_ambiguity else ForestToAmbiguousTreeVisitor - forest_tree_visitor = forest_tree_visitor_cls(self.callbacks, self.forest_sum_visitor and self.forest_sum_visitor()) - - return forest_tree_visitor.visit(solutions[0]) - - -class ApplyCallbacks(Transformer_InPlace): - def __init__(self, postprocess): - self.postprocess = postprocess + if self.tree_class is not None: + # Perform our SPPF -> AST conversion + transformer = ForestToParseTree(self.tree_class, self.callbacks, self.forest_sum_visitor and self.forest_sum_visitor(), self.resolve_ambiguity) + return transformer.transform(solutions[0]) - @v_args(meta=True) - def drv(self, children, meta): - return self.postprocess[meta.rule](children) + # return the root of the SPPF + return solutions[0] diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_common.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_common.py index 6bd614bad..46e242b48 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_common.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_common.py @@ -1,21 +1,8 @@ -"This module implements an Earley Parser" +"""This module implements useful building blocks for the Earley parser +""" -# The parser uses a parse-forest to keep track of derivations and ambiguations. -# When the parse ends successfully, a disambiguation stage resolves all ambiguity -# (right now ambiguity resolution is not developed beyond the needs of lark) -# Afterwards the parse tree is reduced (transformed) according to user callbacks. -# I use the no-recursion version of Transformer, because the tree might be -# deeper than Python's recursion limit (a bit absurd, but that's life) -# -# The algorithm keeps track of each state set, using a corresponding Column instance. -# Column keeps track of new items using NewsList instances. -# -# Author: Erez Shinan (2017) -# Email : erezshin@gmail.com -from ..grammar import NonTerminal, Terminal - -class Item(object): +class Item: "An Earley Item, the atom of the algorithm." __slots__ = ('s', 'rule', 'ptr', 'start', 'is_complete', 'expect', 'previous', 'node', '_hash') @@ -51,25 +38,5 @@ def __repr__(self): return '%s (%d)' % (symbol, self.start) -class TransitiveItem(Item): - __slots__ = ('recognized', 'reduction', 'column', 'next_titem') - def __init__(self, recognized, trule, originator, start): - super(TransitiveItem, self).__init__(trule.rule, trule.ptr, trule.start) - self.recognized = recognized - self.reduction = originator - self.column = start - self.next_titem = None - self._hash = hash((self.s, self.start, self.recognized)) - - def __eq__(self, other): - if not isinstance(other, TransitiveItem): - return False - return self is other or (type(self.s) == type(other.s) and self.s == other.s and self.start == other.start and self.recognized == other.recognized) - - def __hash__(self): - return self._hash - - def __repr__(self): - before = ( expansion.name for expansion in self.rule.expansion[:self.ptr] ) - after = ( expansion.name for expansion in self.rule.expansion[self.ptr:] ) - return '{} : {} -> {}* {} ({}, {})'.format(self.recognized.name, self.rule.origin.name, ' '.join(before), ' '.join(after), self.column, self.start) +# class TransitiveItem(Item): +# ... # removed at commit 4c1cfb2faf24e8f8bff7112627a00b94d261b420 diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_forest.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_forest.py index c8b4f2531..5892c782c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_forest.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/earley_forest.py @@ -4,19 +4,22 @@ in order to store complex ambiguities. Full reference and more details is here: -http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/ +https://web.archive.org/web/20190616123959/http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/ """ from random import randint -from math import isinf from collections import deque from operator import attrgetter from importlib import import_module +from functools import partial +from ..parse_tree_builder import AmbiguousIntermediateExpander +from ..visitors import Discard +from ..lexer import Token +from ..utils import logger from ..tree import Tree -from ..exceptions import ParseError -class ForestNode(object): +class ForestNode: pass class SymbolNode(ForestNode): @@ -32,6 +35,15 @@ class SymbolNode(ForestNode): with each Packed Node child representing a single derivation of a production. Hence a Symbol Node with a single child is unambiguous. + + Parameters: + s: A Symbol, or a tuple of (rule, ptr) for an intermediate node. + start: The index of the start of the substring matched by this symbol (inclusive). + end: The index of the end of the substring matched by this symbol (exclusive). + + Properties: + is_intermediate: True if this node is an intermediate node. + priority: The priority of the node's symbol. """ __slots__ = ('s', 'start', 'end', '_children', 'paths', 'paths_loaded', 'priority', 'is_intermediate', '_hash') def __init__(self, s, start, end): @@ -66,10 +78,13 @@ def load_paths(self): @property def is_ambiguous(self): + """Returns True if this node is ambiguous.""" return len(self.children) > 1 @property def children(self): + """Returns a list of this node's children sorted from greatest to + least priority.""" if not self.paths_loaded: self.load_paths() return sorted(self._children, key=attrgetter('sort_key')) @@ -98,6 +113,13 @@ def __repr__(self): class PackedNode(ForestNode): """ A Packed Node represents a single derivation in a symbol node. + + Parameters: + rule: The rule associated with this node. + parent: The parent of this node. + left: The left child of this node. ``None`` if one does not exist. + right: The right child of this node. ``None`` if one does not exist. + priority: The priority of this node. """ __slots__ = ('parent', 's', 'rule', 'start', 'left', 'right', 'priority', '_hash') def __init__(self, parent, s, rule, start, left, right): @@ -124,8 +146,14 @@ def sort_key(self): """ return self.is_empty, -self.priority, self.rule.order + @property + def children(self): + """Returns a list of this node's children.""" + return [x for x in [self.left, self.right] if x is not None] + def __iter__(self): - return iter([self.left, self.right]) + yield self.left + yield self.right def __eq__(self, other): if not isinstance(other, PackedNode): @@ -146,22 +174,107 @@ def __repr__(self): symbol = self.s.name return "({}, {}, {}, {})".format(symbol, self.start, self.priority, self.rule.order) -class ForestVisitor(object): +class TokenNode(ForestNode): + """ + A Token Node represents a matched terminal and is always a leaf node. + + Parameters: + token: The Token associated with this node. + term: The TerminalDef matched by the token. + priority: The priority of this node. + """ + __slots__ = ('token', 'term', 'priority', '_hash') + def __init__(self, token, term, priority=None): + self.token = token + self.term = term + if priority is not None: + self.priority = priority + else: + self.priority = term.priority if term is not None else 0 + self._hash = hash(token) + + def __eq__(self, other): + if not isinstance(other, TokenNode): + return False + return self is other or (self.token == other.token) + + def __hash__(self): + return self._hash + + def __repr__(self): + return repr(self.token) + +class ForestVisitor: """ An abstract base class for building forest visitors. - Use this as a base when you need to walk the forest. + This class performs a controllable depth-first walk of an SPPF. + The visitor will not enter cycles and will backtrack if one is encountered. + Subclasses are notified of cycles through the ``on_cycle`` method. + + Behavior for visit events is defined by overriding the + ``visit*node*`` functions. + + The walk is controlled by the return values of the ``visit*node_in`` + methods. Returning a node(s) will schedule them to be visited. The visitor + will begin to backtrack if no nodes are returned. + + Parameters: + single_visit: If ``True``, non-Token nodes will only be visited once. """ - __slots__ = ['result'] - def visit_token_node(self, node): pass - def visit_symbol_node_in(self, node): pass - def visit_symbol_node_out(self, node): pass - def visit_packed_node_in(self, node): pass - def visit_packed_node_out(self, node): pass + def __init__(self, single_visit=False): + self.single_visit = single_visit + + def visit_token_node(self, node): + """Called when a ``Token`` is visited. ``Token`` nodes are always leaves.""" + pass + + def visit_symbol_node_in(self, node): + """Called when a symbol node is visited. Nodes that are returned + will be scheduled to be visited. If ``visit_intermediate_node_in`` + is not implemented, this function will be called for intermediate + nodes as well.""" + pass + + def visit_symbol_node_out(self, node): + """Called after all nodes returned from a corresponding ``visit_symbol_node_in`` + call have been visited. If ``visit_intermediate_node_out`` + is not implemented, this function will be called for intermediate + nodes as well.""" + pass + + def visit_packed_node_in(self, node): + """Called when a packed node is visited. Nodes that are returned + will be scheduled to be visited. """ + pass + + def visit_packed_node_out(self, node): + """Called after all nodes returned from a corresponding ``visit_packed_node_in`` + call have been visited.""" + pass + + def on_cycle(self, node, path): + """Called when a cycle is encountered. + + Parameters: + node: The node that causes a cycle. + path: The list of nodes being visited: nodes that have been + entered but not exited. The first element is the root in a forest + visit, and the last element is the node visited most recently. + ``path`` should be treated as read-only. + """ + pass + + def get_cycle_in_path(self, node, path): + """A utility function for use in ``on_cycle`` to obtain a slice of + ``path`` that only contains the nodes that make up the cycle.""" + index = len(path) - 1 + while id(path[index]) != id(node): + index -= 1 + return path[index:] def visit(self, root): - self.result = None # Visiting is a list of IDs of all symbol/intermediate nodes currently in # the stack. It serves two purposes: to detect when we 'recurse' in and out # of a symbol/intermediate so that we can process both up and down. Also, @@ -169,6 +282,13 @@ def visit(self, root): # to recurse into a node that's already on the stack (infinite recursion). visiting = set() + # set of all nodes that have been visited + visited = set() + + # a list of nodes that are currently being visited + # used for the `on_cycle` callback + path = [] + # We do not use recursion here to walk the Forest due to the limited # stack size in python. Therefore input_stack is essentially our stack. input_stack = deque([root]) @@ -179,7 +299,11 @@ def visit(self, root): vpni = getattr(self, 'visit_packed_node_in') vsno = getattr(self, 'visit_symbol_node_out') vsni = getattr(self, 'visit_symbol_node_in') + vino = getattr(self, 'visit_intermediate_node_out', vsno) + vini = getattr(self, 'visit_intermediate_node_in', vsni) vtn = getattr(self, 'visit_token_node') + oc = getattr(self, 'on_cycle') + while input_stack: current = next(reversed(input_stack)) try: @@ -195,37 +319,131 @@ def visit(self, root): continue if id(next_node) in visiting: - raise ParseError("Infinite recursion in grammar, in rule '%s'!" % next_node.s.name) + oc(next_node, path) + continue input_stack.append(next_node) continue - if not isinstance(current, ForestNode): - vtn(current) + if isinstance(current, TokenNode): + vtn(current.token) input_stack.pop() continue current_id = id(current) if current_id in visiting: - if isinstance(current, PackedNode): vpno(current) - else: vsno(current) + if isinstance(current, PackedNode): + vpno(current) + elif current.is_intermediate: + vino(current) + else: + vsno(current) input_stack.pop() + path.pop() visiting.remove(current_id) - continue + visited.add(current_id) + elif self.single_visit and current_id in visited: + input_stack.pop() else: visiting.add(current_id) - if isinstance(current, PackedNode): next_node = vpni(current) - else: next_node = vsni(current) + path.append(current) + if isinstance(current, PackedNode): + next_node = vpni(current) + elif current.is_intermediate: + next_node = vini(current) + else: + next_node = vsni(current) if next_node is None: continue - if id(next_node) in visiting: - raise ParseError("Infinite recursion in grammar!") + if not isinstance(next_node, ForestNode): + next_node = iter(next_node) + elif id(next_node) in visiting: + oc(next_node, path) + continue input_stack.append(next_node) - continue - return self.result +class ForestTransformer(ForestVisitor): + """The base class for a bottom-up forest transformation. Most users will + want to use ``TreeForestTransformer`` instead as it has a friendlier + interface and covers most use cases. + + Transformations are applied via inheritance and overriding of the + ``transform*node`` methods. + + ``transform_token_node`` receives a ``Token`` as an argument. + All other methods receive the node that is being transformed and + a list of the results of the transformations of that node's children. + The return value of these methods are the resulting transformations. + + If ``Discard`` is raised in a node's transformation, no data from that node + will be passed to its parent's transformation. + """ + + def __init__(self): + super(ForestTransformer, self).__init__() + # results of transformations + self.data = dict() + # used to track parent nodes + self.node_stack = deque() + + def transform(self, root): + """Perform a transformation on an SPPF.""" + self.node_stack.append('result') + self.data['result'] = [] + self.visit(root) + assert len(self.data['result']) <= 1 + if self.data['result']: + return self.data['result'][0] + + def transform_symbol_node(self, node, data): + """Transform a symbol node.""" + return node + + def transform_intermediate_node(self, node, data): + """Transform an intermediate node.""" + return node + + def transform_packed_node(self, node, data): + """Transform a packed node.""" + return node + + def transform_token_node(self, node): + """Transform a ``Token``.""" + return node + + def visit_symbol_node_in(self, node): + self.node_stack.append(id(node)) + self.data[id(node)] = [] + return node.children + + def visit_packed_node_in(self, node): + self.node_stack.append(id(node)) + self.data[id(node)] = [] + return node.children + + def visit_token_node(self, node): + transformed = self.transform_token_node(node) + if transformed is not Discard: + self.data[self.node_stack[-1]].append(transformed) + + def _visit_node_out_helper(self, node, method): + self.node_stack.pop() + transformed = method(node, self.data[id(node)]) + if transformed is not Discard: + self.data[self.node_stack[-1]].append(transformed) + del self.data[id(node)] + + def visit_symbol_node_out(self, node): + self._visit_node_out_helper(node, self.transform_symbol_node) + + def visit_intermediate_node_out(self, node): + self._visit_node_out_helper(node, self.transform_intermediate_node) + + def visit_packed_node_out(self, node): + self._visit_node_out_helper(node, self.transform_packed_node) + class ForestSumVisitor(ForestVisitor): """ @@ -243,8 +461,12 @@ class ForestSumVisitor(ForestVisitor): items created during parsing than there are SPPF nodes in the final tree. """ + def __init__(self): + super(ForestSumVisitor, self).__init__(single_visit=True) + def visit_packed_node_in(self, node): - return iter([node.left, node.right]) + yield node.left + yield node.right def visit_symbol_node_in(self, node): return iter(node.children) @@ -258,102 +480,249 @@ def visit_packed_node_out(self, node): def visit_symbol_node_out(self, node): node.priority = max(child.priority for child in node.children) -class ForestToTreeVisitor(ForestVisitor): +class PackedData(): + """Used in transformationss of packed nodes to distinguish the data + that comes from the left child and the right child. """ - A Forest visitor which converts an SPPF forest to an unambiguous AST. - - The implementation in this visitor walks only the first ambiguous child - of each symbol node. When it finds an ambiguous symbol node it first - calls the forest_sum_visitor implementation to sort the children - into preference order using the algorithms defined there; so the first - child should always be the highest preference. The forest_sum_visitor - implementation should be another ForestVisitor which sorts the children - according to some priority mechanism. + + class _NoData(): + pass + + NO_DATA = _NoData() + + def __init__(self, node, data): + self.left = self.NO_DATA + self.right = self.NO_DATA + if data: + if node.left is not None: + self.left = data[0] + if len(data) > 1: + self.right = data[1] + else: + self.right = data[0] + +class ForestToParseTree(ForestTransformer): + """Used by the earley parser when ambiguity equals 'resolve' or + 'explicit'. Transforms an SPPF into an (ambiguous) parse tree. + + Parameters: + tree_class: The tree class to use for construction + callbacks: A dictionary of rules to functions that output a tree + prioritizer: A ``ForestVisitor`` that manipulates the priorities of ForestNodes + resolve_ambiguity: If True, ambiguities will be resolved based on + priorities. Otherwise, `_ambig` nodes will be in the resulting tree. + use_cache: If True, the results of packed node transformations will be cached. """ - __slots__ = ['forest_sum_visitor', 'callbacks', 'output_stack'] - def __init__(self, callbacks, forest_sum_visitor = None): - assert callbacks - self.forest_sum_visitor = forest_sum_visitor + + def __init__(self, tree_class=Tree, callbacks=dict(), prioritizer=ForestSumVisitor(), resolve_ambiguity=True, use_cache=True): + super(ForestToParseTree, self).__init__() + self.tree_class = tree_class self.callbacks = callbacks + self.prioritizer = prioritizer + self.resolve_ambiguity = resolve_ambiguity + self._use_cache = use_cache + self._cache = {} + self._on_cycle_retreat = False + self._cycle_node = None + self._successful_visits = set() def visit(self, root): - self.output_stack = deque() - return super(ForestToTreeVisitor, self).visit(root) + if self.prioritizer: + self.prioritizer.visit(root) + super(ForestToParseTree, self).visit(root) + self._cache = {} + + def on_cycle(self, node, path): + logger.debug("Cycle encountered in the SPPF at node: %s. " + "As infinite ambiguities cannot be represented in a tree, " + "this family of derivations will be discarded.", node) + self._cycle_node = node + self._on_cycle_retreat = True + + def _check_cycle(self, node): + if self._on_cycle_retreat: + if id(node) == id(self._cycle_node) or id(node) in self._successful_visits: + self._cycle_node = None + self._on_cycle_retreat = False + else: + return Discard - def visit_token_node(self, node): - self.output_stack[-1].append(node) + def _collapse_ambig(self, children): + new_children = [] + for child in children: + if hasattr(child, 'data') and child.data == '_ambig': + new_children += child.children + else: + new_children.append(child) + return new_children + + def _call_rule_func(self, node, data): + # called when transforming children of symbol nodes + # data is a list of trees or tokens that correspond to the + # symbol's rule expansion + return self.callbacks[node.rule](data) + + def _call_ambig_func(self, node, data): + # called when transforming a symbol node + # data is a list of trees where each tree's data is + # equal to the name of the symbol or one of its aliases. + if len(data) > 1: + return self.tree_class('_ambig', data) + elif data: + return data[0] + return Discard + + def transform_symbol_node(self, node, data): + if id(node) not in self._successful_visits: + return Discard + r = self._check_cycle(node) + if r is Discard: + return r + self._successful_visits.remove(id(node)) + data = self._collapse_ambig(data) + return self._call_ambig_func(node, data) + + def transform_intermediate_node(self, node, data): + if id(node) not in self._successful_visits: + return Discard + r = self._check_cycle(node) + if r is Discard: + return r + self._successful_visits.remove(id(node)) + if len(data) > 1: + children = [self.tree_class('_inter', c) for c in data] + return self.tree_class('_iambig', children) + return data[0] + + def transform_packed_node(self, node, data): + r = self._check_cycle(node) + if r is Discard: + return r + if self.resolve_ambiguity and id(node.parent) in self._successful_visits: + return Discard + if self._use_cache and id(node) in self._cache: + return self._cache[id(node)] + children = [] + assert len(data) <= 2 + data = PackedData(node, data) + if data.left is not PackedData.NO_DATA: + if node.left.is_intermediate and isinstance(data.left, list): + children += data.left + else: + children.append(data.left) + if data.right is not PackedData.NO_DATA: + children.append(data.right) + if node.parent.is_intermediate: + return self._cache.setdefault(id(node), children) + return self._cache.setdefault(id(node), self._call_rule_func(node, children)) def visit_symbol_node_in(self, node): - if self.forest_sum_visitor and node.is_ambiguous and isinf(node.priority): - self.forest_sum_visitor.visit(node) - return next(iter(node.children)) + super(ForestToParseTree, self).visit_symbol_node_in(node) + if self._on_cycle_retreat: + return + return node.children def visit_packed_node_in(self, node): - if not node.parent.is_intermediate: - self.output_stack.append([]) - return iter([node.left, node.right]) + self._on_cycle_retreat = False + to_visit = super(ForestToParseTree, self).visit_packed_node_in(node) + if not self.resolve_ambiguity or id(node.parent) not in self._successful_visits: + if not self._use_cache or id(node) not in self._cache: + return to_visit def visit_packed_node_out(self, node): - if not node.parent.is_intermediate: - result = self.callbacks[node.rule](self.output_stack.pop()) - if self.output_stack: - self.output_stack[-1].append(result) - else: - self.result = result - -class ForestToAmbiguousTreeVisitor(ForestToTreeVisitor): - """ - A Forest visitor which converts an SPPF forest to an ambiguous AST. - - Because of the fundamental disparity between what can be stored in - an SPPF and what can be stored in a Tree; this implementation is not - complete. It correctly deals with ambiguities that occur on symbol nodes only, - and cannot deal with ambiguities that occur on intermediate nodes. - - Usually, most parsers can be rewritten to avoid intermediate node - ambiguities. Also, this implementation could be fixed, however - the code to handle intermediate node ambiguities is messy and - would not be performant. It is much better not to use this and - instead to correctly disambiguate the forest and only store unambiguous - parses in Trees. It is here just to provide some parity with the - old ambiguity='explicit'. - - This is mainly used by the test framework, to make it simpler to write - tests ensuring the SPPF contains the right results. + super(ForestToParseTree, self).visit_packed_node_out(node) + if not self._on_cycle_retreat: + self._successful_visits.add(id(node.parent)) + +def handles_ambiguity(func): + """Decorator for methods of subclasses of ``TreeForestTransformer``. + Denotes that the method should receive a list of transformed derivations.""" + func.handles_ambiguity = True + return func + +class TreeForestTransformer(ForestToParseTree): + """A ``ForestTransformer`` with a tree ``Transformer``-like interface. + By default, it will construct a tree. + + Methods provided via inheritance are called based on the rule/symbol + names of nodes in the forest. + + Methods that act on rules will receive a list of the results of the + transformations of the rule's children. By default, trees and tokens. + + Methods that act on tokens will receive a token. + + Alternatively, methods that act on rules may be annotated with + ``handles_ambiguity``. In this case, the function will receive a list + of all the transformations of all the derivations of the rule. + By default, a list of trees where each tree.data is equal to the + rule name or one of its aliases. + + Non-tree transformations are made possible by override of + ``__default__``, ``__default_token__``, and ``__default_ambig__``. + + Note: + Tree shaping features such as inlined rules and token filtering are + not built into the transformation. Positions are also not propagated. + + Parameters: + tree_class: The tree class to use for construction + prioritizer: A ``ForestVisitor`` that manipulates the priorities of nodes in the SPPF. + resolve_ambiguity: If True, ambiguities will be resolved based on priorities. + use_cache (bool): If True, caches the results of some transformations, + potentially improving performance when ``resolve_ambiguity==False``. + Only use if you know what you are doing: i.e. All transformation + functions are pure and referentially transparent. """ - def __init__(self, callbacks, forest_sum_visitor = ForestSumVisitor): - super(ForestToAmbiguousTreeVisitor, self).__init__(callbacks, forest_sum_visitor) - def visit_token_node(self, node): - self.output_stack[-1].children.append(node) + def __init__(self, tree_class=Tree, prioritizer=ForestSumVisitor(), resolve_ambiguity=True, use_cache=False): + super(TreeForestTransformer, self).__init__(tree_class, dict(), prioritizer, resolve_ambiguity, use_cache) - def visit_symbol_node_in(self, node): - if self.forest_sum_visitor and node.is_ambiguous and isinf(node.priority): - self.forest_sum_visitor.visit(node) - if not node.is_intermediate and node.is_ambiguous: - self.output_stack.append(Tree('_ambig', [])) - return iter(node.children) + def __default__(self, name, data): + """Default operation on tree (for override). - def visit_symbol_node_out(self, node): - if not node.is_intermediate and node.is_ambiguous: - result = self.output_stack.pop() - if self.output_stack: - self.output_stack[-1].children.append(result) - else: - self.result = result + Returns a tree with name with data as children. + """ + return self.tree_class(name, data) - def visit_packed_node_in(self, node): - if not node.parent.is_intermediate: - self.output_stack.append(Tree('drv', [])) - return iter([node.left, node.right]) + def __default_ambig__(self, name, data): + """Default operation on ambiguous rule (for override). - def visit_packed_node_out(self, node): - if not node.parent.is_intermediate: - result = self.callbacks[node.rule](self.output_stack.pop().children) - if self.output_stack: - self.output_stack[-1].children.append(result) - else: - self.result = result + Wraps data in an '_ambig_' node if it contains more than + one element. + """ + if len(data) > 1: + return self.tree_class('_ambig', data) + elif data: + return data[0] + return Discard + + def __default_token__(self, node): + """Default operation on ``Token`` (for override). + + Returns ``node``. + """ + return node + + def transform_token_node(self, node): + return getattr(self, node.type, self.__default_token__)(node) + + def _call_rule_func(self, node, data): + name = node.rule.alias or node.rule.options.template_source or node.rule.origin.name + user_func = getattr(self, name, self.__default__) + if user_func == self.__default__ or hasattr(user_func, 'handles_ambiguity'): + user_func = partial(self.__default__, name) + if not self.resolve_ambiguity: + wrapper = partial(AmbiguousIntermediateExpander, self.tree_class) + user_func = wrapper(user_func) + return user_func(data) + + def _call_ambig_func(self, node, data): + name = node.s.name + user_func = getattr(self, name, self.__default_ambig__) + if user_func == self.__default_ambig__ or not hasattr(user_func, 'handles_ambiguity'): + user_func = partial(self.__default_ambig__, name) + return user_func(data) class ForestToPyDotVisitor(ForestVisitor): """ @@ -365,12 +734,16 @@ class ForestToPyDotVisitor(ForestVisitor): is structured. """ def __init__(self, rankdir="TB"): + super(ForestToPyDotVisitor, self).__init__(single_visit=True) self.pydot = import_module('pydot') self.graph = self.pydot.Dot(graph_type='digraph', rankdir=rankdir) def visit(self, root, filename): super(ForestToPyDotVisitor, self).visit(root) - self.graph.write_png(filename) + try: + self.graph.write_png(filename) + except FileNotFoundError as e: + logger.error("Could not write png: ", e) def visit_token_node(self, node): graph_node_id = str(id(node)) @@ -389,14 +762,15 @@ def visit_packed_node_in(self, node): graph_node_shape = "diamond" graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label) self.graph.add_node(graph_node) - return iter([node.left, node.right]) + yield node.left + yield node.right def visit_packed_node_out(self, node): graph_node_id = str(id(node)) graph_node = self.graph.get_node(graph_node_id)[0] for child in [node.left, node.right]: if child is not None: - child_graph_node_id = str(id(child)) + child_graph_node_id = str(id(child.token if isinstance(child, TokenNode) else child)) child_graph_node = self.graph.get_node(child_graph_node_id)[0] self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node)) else: diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/grammar_analysis.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/grammar_analysis.py index 94c32ccc3..b526e470a 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/grammar_analysis.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/grammar_analysis.py @@ -5,7 +5,7 @@ from ..grammar import Rule, Terminal, NonTerminal -class RulePtr(object): +class RulePtr: __slots__ = ('rule', 'index') def __init__(self, rule, index): @@ -38,7 +38,7 @@ def __hash__(self): # state generation ensures no duplicate LR0ItemSets -class LR0ItemSet(object): +class LR0ItemSet: __slots__ = ('kernel', 'closure', 'transitions', 'lookaheads') def __init__(self, kernel, closure): @@ -121,7 +121,7 @@ def calculate_sets(rules): return FIRST, FOLLOW, NULLABLE -class GrammarAnalyzer(object): +class GrammarAnalyzer: def __init__(self, parser_conf, debug=False): self.debug = debug @@ -138,7 +138,7 @@ def __init__(self, parser_conf, debug=False): for r in rules: for sym in r.expansion: if not (sym.is_term or sym in self.rules_by_origin): - raise GrammarError("Using an undefined rule: %s" % sym) # TODO test validation + raise GrammarError("Using an undefined rule: %s" % sym) self.start_states = {start: self.expand_rule(root_rule.origin) for start, root_rule in root_rules.items()} diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_analysis.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_analysis.py index 8890c3cd2..216371e5d 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_analysis.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_analysis.py @@ -6,10 +6,9 @@ # Author: Erez Shinan (2017) # Email : erezshin@gmail.com -import logging -from collections import defaultdict, deque +from collections import defaultdict -from ..utils import classify, classify_bool, bfs, fzset, Serialize, Enumerator +from ..utils import classify, classify_bool, bfs, fzset, Enumerator, logger from ..exceptions import GrammarError from .grammar_analysis import GrammarAnalyzer, Terminal, LR0ItemSet @@ -37,7 +36,6 @@ def __init__(self, states, start_states, end_states): def serialize(self, memo): tokens = Enumerator() - rules = Enumerator() states = { state: {tokens.get(token): ((1, arg.serialize(memo)) if action is Reduce else (0, arg)) @@ -247,21 +245,38 @@ def compute_lookaheads(self): def compute_lalr1_states(self): m = {} + reduce_reduce = [] for state in self.lr0_states: actions = {} for la, next_state in state.transitions.items(): actions[la] = (Shift, next_state.closure) for la, rules in state.lookaheads.items(): if len(rules) > 1: - raise GrammarError('Reduce/Reduce collision in %s between the following rules: %s' % (la, ''.join([ '\n\t\t- ' + str(r) for r in rules ]))) + # Try to resolve conflict based on priority + p = [(r.options.priority or 0, r) for r in rules] + p.sort(key=lambda r: r[0], reverse=True) + best, second_best = p[:2] + if best[0] > second_best[0]: + rules = [best[1]] + else: + reduce_reduce.append((state, la, rules)) if la in actions: if self.debug: - logging.warning('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name) - logging.warning(' * %s', list(rules)[0]) + logger.warning('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name) + logger.warning(' * %s', list(rules)[0]) else: actions[la] = (Reduce, list(rules)[0]) m[state] = { k.name: v for k, v in actions.items() } + if reduce_reduce: + msgs = [] + for state, la, rules in reduce_reduce: + msg = 'Reduce/Reduce collision in %s between the following rules: %s' % (la, ''.join([ '\n\t- ' + str(r) for r in rules ])) + if self.debug: + msg += '\n collision occurred in state: {%s\n }' % ''.join(['\n\t' + str(x) for x in state.closure]) + msgs.append(msg) + raise GrammarError('\n\n'.join(msgs)) + states = { k.closure: v for k, v in m.items() } # compute end states @@ -270,7 +285,7 @@ def compute_lalr1_states(self): for rp in state: for start in self.lr0_start_states: if rp.rule.origin.name == ('$root_' + start) and rp.is_satisfied: - assert(not start in end_states) + assert(start not in end_states) end_states[start] = state _parse_table = ParseTable(states, { start: state.closure for start, state in self.lr0_start_states.items() }, end_states) @@ -285,4 +300,4 @@ def compute_lalr(self): self.compute_reads_relations() self.compute_includes_lookback() self.compute_lookaheads() - self.compute_lalr1_states() \ No newline at end of file + self.compute_lalr1_states() diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_interactive_parser.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_interactive_parser.py new file mode 100644 index 000000000..0013ddf3f --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_interactive_parser.py @@ -0,0 +1,148 @@ +# This module provides a LALR interactive parser, which is used for debugging and error handling + +from typing import Iterator, List +from copy import copy +import warnings + +from lark.exceptions import UnexpectedToken +from lark.lexer import Token, LexerThread + + +class InteractiveParser: + """InteractiveParser gives you advanced control over parsing and error handling when parsing with LALR. + + For a simpler interface, see the ``on_error`` argument to ``Lark.parse()``. + """ + def __init__(self, parser, parser_state, lexer_thread: LexerThread): + self.parser = parser + self.parser_state = parser_state + self.lexer_thread = lexer_thread + self.result = None + + @property + def lexer_state(self) -> LexerThread: + warnings.warn("lexer_state will be removed in subsequent releases. Use lexer_thread instead.", DeprecationWarning) + return self.lexer_thread + + def feed_token(self, token: Token): + """Feed the parser with a token, and advance it to the next state, as if it received it from the lexer. + + Note that ``token`` has to be an instance of ``Token``. + """ + return self.parser_state.feed_token(token, token.type == '$END') + + def iter_parse(self) -> Iterator[Token]: + """Step through the different stages of the parse, by reading tokens from the lexer + and feeding them to the parser, one per iteration. + + Returns an iterator of the tokens it encounters. + + When the parse is over, the resulting tree can be found in ``InteractiveParser.result``. + """ + for token in self.lexer_thread.lex(self.parser_state): + yield token + self.result = self.feed_token(token) + + def exhaust_lexer(self) -> List[Token]: + """Try to feed the rest of the lexer state into the interactive parser. + + Note that this modifies the instance in place and does not feed an '$END' Token + """ + return list(self.iter_parse()) + + + def feed_eof(self, last_token=None): + """Feed a '$END' Token. Borrows from 'last_token' if given.""" + eof = Token.new_borrow_pos('$END', '', last_token) if last_token is not None else self.lexer_thread._Token('$END', '', 0, 1, 1) + return self.feed_token(eof) + + + def __copy__(self): + """Create a new interactive parser with a separate state. + + Calls to feed_token() won't affect the old instance, and vice-versa. + """ + return type(self)( + self.parser, + copy(self.parser_state), + copy(self.lexer_thread), + ) + + def copy(self): + return copy(self) + + def __eq__(self, other): + if not isinstance(other, InteractiveParser): + return False + + return self.parser_state == other.parser_state and self.lexer_thread == other.lexer_thread + + def as_immutable(self): + """Convert to an ``ImmutableInteractiveParser``.""" + p = copy(self) + return ImmutableInteractiveParser(p.parser, p.parser_state, p.lexer_thread) + + def pretty(self): + """Print the output of ``choices()`` in a way that's easier to read.""" + out = ["Parser choices:"] + for k, v in self.choices().items(): + out.append('\t- %s -> %r' % (k, v)) + out.append('stack size: %s' % len(self.parser_state.state_stack)) + return '\n'.join(out) + + def choices(self): + """Returns a dictionary of token types, matched to their action in the parser. + + Only returns token types that are accepted by the current state. + + Updated by ``feed_token()``. + """ + return self.parser_state.parse_conf.parse_table.states[self.parser_state.position] + + def accepts(self): + """Returns the set of possible tokens that will advance the parser into a new valid state.""" + accepts = set() + for t in self.choices(): + if t.isupper(): # is terminal? + new_cursor = copy(self) + try: + new_cursor.feed_token(self.lexer_thread._Token(t, '')) + except UnexpectedToken: + pass + else: + accepts.add(t) + return accepts + + def resume_parse(self): + """Resume automated parsing from the current state.""" + return self.parser.parse_from_state(self.parser_state) + + + +class ImmutableInteractiveParser(InteractiveParser): + """Same as ``InteractiveParser``, but operations create a new instance instead + of changing it in-place. + """ + + result = None + + def __hash__(self): + return hash((self.parser_state, self.lexer_thread)) + + def feed_token(self, token): + c = copy(self) + c.result = InteractiveParser.feed_token(c, token) + return c + + def exhaust_lexer(self): + """Try to feed the rest of the lexer state into the parser. + + Note that this returns a new ImmutableInteractiveParser and does not feed an '$END' Token""" + cursor = self.as_mutable() + cursor.exhaust_lexer() + return cursor.as_immutable() + + def as_mutable(self): + """Convert to an ``InteractiveParser``.""" + p = copy(self) + return InteractiveParser(p.parser, p.parser_state, p.lexer_thread) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_parser.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_parser.py index f26cbc5b0..c89c49df6 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_parser.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_parser.py @@ -2,18 +2,19 @@ """ # Author: Erez Shinan (2017) # Email : erezshin@gmail.com -from ..exceptions import UnexpectedToken +from copy import deepcopy, copy +from typing import Dict, Any from ..lexer import Token -from ..utils import Enumerator, Serialize +from ..utils import Serialize from .lalr_analysis import LALR_Analyzer, Shift, Reduce, IntParseTable -from .lalr_puppet import ParserPuppet +from .lalr_interactive_parser import InteractiveParser +from lark.exceptions import UnexpectedCharacters, UnexpectedInput, UnexpectedToken ###{standalone -class LALR_Parser(object): +class LALR_Parser(Serialize): def __init__(self, parser_conf, debug=False): - assert all(r.options.priority is None for r in parser_conf.rules), "LALR doesn't yet support prioritization" analysis = LALR_Analyzer(parser_conf, debug=debug) analysis.compute_lalr() callbacks = parser_conf.callbacks @@ -23,97 +24,176 @@ def __init__(self, parser_conf, debug=False): self.parser = _Parser(analysis.parse_table, callbacks, debug) @classmethod - def deserialize(cls, data, memo, callbacks): + def deserialize(cls, data, memo, callbacks, debug=False): inst = cls.__new__(cls) inst._parse_table = IntParseTable.deserialize(data, memo) - inst.parser = _Parser(inst._parse_table, callbacks) + inst.parser = _Parser(inst._parse_table, callbacks, debug) return inst - def serialize(self, memo): + def serialize(self, memo: Any = None) -> Dict[str, Any]: return self._parse_table.serialize(memo) - def parse(self, *args): - return self.parser.parse(*args) + def parse_interactive(self, lexer, start): + return self.parser.parse(lexer, start, start_interactive=True) + def parse(self, lexer, start, on_error=None): + try: + return self.parser.parse(lexer, start) + except UnexpectedInput as e: + if on_error is None: + raise -class _Parser: - def __init__(self, parse_table, callbacks, debug=False): - self.parse_table = parse_table - self.callbacks = callbacks - self.debug = debug + while True: + if isinstance(e, UnexpectedCharacters): + s = e.interactive_parser.lexer_thread.state + p = s.line_ctr.char_pos + + if not on_error(e): + raise e - def parse(self, seq, start, set_state=None, value_stack=None, state_stack=None): - token = None - stream = iter(seq) - states = self.parse_table.states - start_state = self.parse_table.start_states[start] - end_state = self.parse_table.end_states[start] + if isinstance(e, UnexpectedCharacters): + # If user didn't change the character position, then we should + if p == s.line_ctr.char_pos: + s.line_ctr.feed(s.text[p:p+1]) - state_stack = state_stack or [start_state] - value_stack = value_stack or [] + try: + return e.interactive_parser.resume_parse() + except UnexpectedToken as e2: + if (isinstance(e, UnexpectedToken) + and e.token.type == e2.token.type == '$END' + and e.interactive_parser == e2.interactive_parser): + # Prevent infinite loop + raise e2 + e = e2 + except UnexpectedCharacters as e2: + e = e2 + + +class ParseConf: + __slots__ = 'parse_table', 'callbacks', 'start', 'start_state', 'end_state', 'states' + + def __init__(self, parse_table, callbacks, start): + self.parse_table = parse_table - if set_state: set_state(start_state) + self.start_state = self.parse_table.start_states[start] + self.end_state = self.parse_table.end_states[start] + self.states = self.parse_table.states + + self.callbacks = callbacks + self.start = start + + +class ParserState: + __slots__ = 'parse_conf', 'lexer', 'state_stack', 'value_stack' + + def __init__(self, parse_conf, lexer, state_stack=None, value_stack=None): + self.parse_conf = parse_conf + self.lexer = lexer + self.state_stack = state_stack or [self.parse_conf.start_state] + self.value_stack = value_stack or [] + + @property + def position(self): + return self.state_stack[-1] + + # Necessary for match_examples() to work + def __eq__(self, other): + if not isinstance(other, ParserState): + return NotImplemented + return len(self.state_stack) == len(other.state_stack) and self.position == other.position + + def __copy__(self): + return type(self)( + self.parse_conf, + self.lexer, # XXX copy + copy(self.state_stack), + deepcopy(self.value_stack), + ) + + def copy(self): + return copy(self) + + def feed_token(self, token, is_end=False): + state_stack = self.state_stack + value_stack = self.value_stack + states = self.parse_conf.states + end_state = self.parse_conf.end_state + callbacks = self.parse_conf.callbacks - def get_action(token): + while True: state = state_stack[-1] try: - return states[state][token.type] + action, arg = states[state][token.type] except KeyError: - expected = [s for s in states[state].keys() if s.isupper()] - try: - puppet = ParserPuppet(self, state_stack, value_stack, start, stream, set_state) - except NameError: - puppet = None - raise UnexpectedToken(token, expected, state=state, puppet=puppet) - - def reduce(rule): - size = len(rule.expansion) - if size: - s = value_stack[-size:] - del state_stack[-size:] - del value_stack[-size:] + expected = {s for s in states[state].keys() if s.isupper()} + raise UnexpectedToken(token, expected, state=self, interactive_parser=None) + + assert arg != end_state + + if action is Shift: + # shift once and return + assert not is_end + state_stack.append(arg) + value_stack.append(token if token.type not in callbacks else callbacks[token.type](token)) + return else: - s = [] + # reduce+shift as many times as necessary + rule = arg + size = len(rule.expansion) + if size: + s = value_stack[-size:] + del state_stack[-size:] + del value_stack[-size:] + else: + s = [] + + value = callbacks[rule](s) + + _action, new_state = states[state_stack[-1]][rule.origin.name] + assert _action is Shift + state_stack.append(new_state) + value_stack.append(value) + + if is_end and state_stack[-1] == end_state: + return value_stack[-1] + +class _Parser: + def __init__(self, parse_table, callbacks, debug=False): + self.parse_table = parse_table + self.callbacks = callbacks + self.debug = debug - value = self.callbacks[rule](s) + def parse(self, lexer, start, value_stack=None, state_stack=None, start_interactive=False): + parse_conf = ParseConf(self.parse_table, self.callbacks, start) + parser_state = ParserState(parse_conf, lexer, state_stack, value_stack) + if start_interactive: + return InteractiveParser(self, parser_state, parser_state.lexer) + return self.parse_from_state(parser_state) - _action, new_state = states[state_stack[-1]][rule.origin.name] - assert _action is Shift - state_stack.append(new_state) - value_stack.append(value) + def parse_from_state(self, state): # Main LALR-parser loop try: - for token in stream: - while True: - action, arg = get_action(token) - assert arg != end_state - - if action is Shift: - state_stack.append(arg) - value_stack.append(token) - if set_state: set_state(arg) - break # next token - else: - reduce(arg) + token = None + for token in state.lexer.lex(state): + state.feed_token(token) + + end_token = Token.new_borrow_pos('$END', '', token) if token else Token('$END', '', 0, 1, 1) + return state.feed_token(end_token, True) + except UnexpectedInput as e: + try: + e.interactive_parser = InteractiveParser(self, state, state.lexer) + except NameError: + pass + raise e except Exception as e: if self.debug: print("") print("STATE STACK DUMP") print("----------------") - for i, s in enumerate(state_stack): + for i, s in enumerate(state.state_stack): print('%d)' % i , s) print("") raise - - token = Token.new_borrow_pos('$END', '', token) if token else Token('$END', '', 0, 1, 1) - while True: - _action, arg = get_action(token) - assert(_action is Reduce) - reduce(arg) - if state_stack[-1] == end_state: - return value_stack[-1] - ###} - diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_puppet.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_puppet.py deleted file mode 100644 index 968783cc4..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/lalr_puppet.py +++ /dev/null @@ -1,79 +0,0 @@ -# This module provide a LALR puppet, which is used to debugging and error handling - -from copy import deepcopy - -from .lalr_analysis import Shift, Reduce - -class ParserPuppet: - def __init__(self, parser, state_stack, value_stack, start, stream, set_state): - self.parser = parser - self._state_stack = state_stack - self._value_stack = value_stack - self._start = start - self._stream = stream - self._set_state = set_state - - self.result = None - - def feed_token(self, token): - """Advance the parser state, as if it just recieved `token` from the lexer - - """ - end_state = self.parser.parse_table.end_states[self._start] - state_stack = self._state_stack - value_stack = self._value_stack - - state = state_stack[-1] - action, arg = self.parser.parse_table.states[state][token.type] - assert arg != end_state - - while action is Reduce: - rule = arg - size = len(rule.expansion) - if size: - s = value_stack[-size:] - del state_stack[-size:] - del value_stack[-size:] - else: - s = [] - - value = self.parser.callbacks[rule](s) - - _action, new_state = self.parser.parse_table.states[state_stack[-1]][rule.origin.name] - assert _action is Shift - state_stack.append(new_state) - value_stack.append(value) - - if state_stack[-1] == end_state: - self.result = value_stack[-1] - return self.result - - state = state_stack[-1] - action, arg = self.parser.parse_table.states[state][token.type] - assert arg != end_state - - assert action is Shift - state_stack.append(arg) - value_stack.append(token) - - def copy(self): - return type(self)( - self.parser, - list(self._state_stack), - deepcopy(self._value_stack), - self._start, - self._stream, - self._set_state, - ) - - def pretty(): - print("Puppet choices:") - for k, v in self.choices.items(): - print('\t-', k, '->', v) - print('stack size:', len(self._state_stack)) - - def choices(self): - return self.parser.parse_table.states[self._state_stack[-1]] - - def resume_parse(self): - return self.parser.parse(self._stream, self._start, self._set_state, self._value_stack, self._state_stack) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/resolve_ambig.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/resolve_ambig.py new file mode 100644 index 000000000..2470eb978 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/resolve_ambig.py @@ -0,0 +1,109 @@ +from ..utils import compare +from functools import cmp_to_key + +from ..tree import Tree + + +# Standard ambiguity resolver (uses comparison) +# +# Author: Erez Sh + +def _compare_rules(rule1, rule2): + return -compare( len(rule1.expansion), len(rule2.expansion)) + +def _sum_priority(tree): + p = 0 + + for n in tree.iter_subtrees(): + try: + p += n.meta.rule.options.priority or 0 + except AttributeError: + pass + + return p + +def _compare_priority(tree1, tree2): + tree1.iter_subtrees() + +def _compare_drv(tree1, tree2): + try: + rule1 = tree1.meta.rule + except AttributeError: + rule1 = None + + try: + rule2 = tree2.meta.rule + except AttributeError: + rule2 = None + + if None == rule1 == rule2: + return compare(tree1, tree2) + elif rule1 is None: + return -1 + elif rule2 is None: + return 1 + + assert tree1.data != '_ambig' + assert tree2.data != '_ambig' + + p1 = _sum_priority(tree1) + p2 = _sum_priority(tree2) + c = (p1 or p2) and compare(p1, p2) + if c: + return c + + c = _compare_rules(tree1.meta.rule, tree2.meta.rule) + if c: + return c + + # rules are "equal", so compare trees + if len(tree1.children) == len(tree2.children): + for t1, t2 in zip(tree1.children, tree2.children): + c = _compare_drv(t1, t2) + if c: + return c + + return compare(len(tree1.children), len(tree2.children)) + + +def _standard_resolve_ambig(tree): + assert tree.data == '_ambig' + key_f = cmp_to_key(_compare_drv) + best = max(tree.children, key=key_f) + assert best.data == 'drv' + tree.set('drv', best.children) + tree.meta.rule = best.meta.rule # needed for applying callbacks + +def standard_resolve_ambig(tree): + for ambig in tree.find_data('_ambig'): + _standard_resolve_ambig(ambig) + + return tree + + + + +# Anti-score Sum +# +# Author: Uriva (https://github.com/uriva) + +def _antiscore_sum_drv(tree): + if not isinstance(tree, Tree): + return 0 + + assert tree.data != '_ambig' + + return _sum_priority(tree) + +def _antiscore_sum_resolve_ambig(tree): + assert tree.data == '_ambig' + best = min(tree.children, key=_antiscore_sum_drv) + assert best.data == 'drv' + tree.set('drv', best.children) + tree.meta.rule = best.meta.rule # needed for applying callbacks + +def antiscore_sum_resolve_ambig(tree): + for ambig in tree.find_data('_ambig'): + _antiscore_sum_resolve_ambig(ambig) + + return tree diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/xearley.py b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/xearley.py index 855625a96..343e5c0b6 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/xearley.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/parsers/xearley.py @@ -16,17 +16,18 @@ from collections import defaultdict +from ..tree import Tree from ..exceptions import UnexpectedCharacters from ..lexer import Token from ..grammar import Terminal from .earley import Parser as BaseParser -from .earley_forest import SymbolNode +from .earley_forest import SymbolNode, TokenNode class Parser(BaseParser): - def __init__(self, parser_conf, term_matcher, resolve_ambiguity=True, ignore = (), complete_lex = False, debug=False): - BaseParser.__init__(self, parser_conf, term_matcher, resolve_ambiguity, debug) - self.ignore = [Terminal(t) for t in ignore] + def __init__(self, lexer_conf, parser_conf, term_matcher, resolve_ambiguity=True, complete_lex = False, debug=False, tree_class=Tree): + BaseParser.__init__(self, lexer_conf, parser_conf, term_matcher, resolve_ambiguity, debug, tree_class) + self.ignore = [Terminal(t) for t in lexer_conf.ignore] self.complete_lex = complete_lex def _parse(self, stream, columns, to_scan, start_symbol=None): @@ -62,9 +63,10 @@ def scan(i, to_scan): t = Token(item.expect.name, m.group(0), i, text_line, text_column) delayed_matches[i+m.end()].append( (item, i, t) ) - # Remove any items that successfully matched in this pass from the to_scan buffer. - # This ensures we don't carry over tokens that already matched, if we're ignoring below. - to_scan.remove(item) + # XXX The following 3 lines were commented out for causing a bug. See issue #768 + # # Remove any items that successfully matched in this pass from the to_scan buffer. + # # This ensures we don't carry over tokens that already matched, if we're ignoring below. + # to_scan.remove(item) # 3) Process any ignores. This is typically used for e.g. whitespace. # We carry over any unmatched items from the to_scan buffer to be matched again after @@ -97,8 +99,9 @@ def scan(i, to_scan): new_item = item.advance() label = (new_item.s, new_item.start, i) + token_node = TokenNode(token, terminals[token.type]) new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label)) - new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token) + new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node) else: new_item = item @@ -112,13 +115,18 @@ def scan(i, to_scan): del delayed_matches[i+1] # No longer needed, so unburden memory if not next_set and not delayed_matches and not next_to_scan: - raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan}, set(to_scan)) + considered_rules = list(sorted(to_scan, key=lambda key: key.rule.origin.name)) + raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan}, + set(to_scan), state=frozenset(i.s for i in to_scan), + considered_rules=considered_rules + ) return next_to_scan delayed_matches = defaultdict(list) match = self.term_matcher + terminals = self.lexer_conf.terminals_by_name # Cache for nodes & tokens created in a particular parse step. transitives = [{}] @@ -148,4 +156,4 @@ def scan(i, to_scan): ## Column is now the final column in the parse. assert i == len(columns)-1 - return to_scan \ No newline at end of file + return to_scan diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/py.typed b/conda_lock/_vendor/poetry/core/_vendor/lark/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct.py b/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct.py index 1e3adc77c..906ca8122 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct.py @@ -1,16 +1,15 @@ -from collections import defaultdict - -from .tree import Tree -from .visitors import Transformer_InPlace -from .common import ParserConf -from .lexer import Token, PatternStr -from .parsers import earley -from .grammar import Rule, Terminal, NonTerminal +"""Reconstruct text from a tree, based on Lark grammar""" +from typing import List, Dict, Union, Callable, Iterable, Optional +from .lark import Lark +from .tree import Tree, ParseTree +from .visitors import Transformer_InPlace +from .lexer import Token, PatternStr, TerminalDef +from .grammar import Terminal, NonTerminal, Symbol -def is_discarded_terminal(t): - return t.is_term and t.filter_out +from .tree_matcher import TreeMatcher, is_discarded_terminal +from .utils import is_id_continue def is_iter_empty(i): try: @@ -23,7 +22,10 @@ def is_iter_empty(i): class WriteTokensTransformer(Transformer_InPlace): "Inserts discarded tokens into their correct place, according to the rules of grammar" - def __init__(self, tokens, term_subs): + tokens: Dict[str, TerminalDef] + term_subs: Dict[str, Callable[[Symbol], str]] + + def __init__(self, tokens: Dict[str, TerminalDef], term_subs: Dict[str, Callable[[Symbol], str]]) -> None: self.tokens = tokens self.term_subs = term_subs @@ -59,105 +61,45 @@ def __default__(self, data, children, meta): return to_write -class MatchTree(Tree): - pass - -class MakeMatchTree: - def __init__(self, name, expansion): - self.name = name - self.expansion = expansion - - def __call__(self, args): - t = MatchTree(self.name, args) - t.meta.match_tree = True - t.meta.orig_expansion = self.expansion - return t - -def best_from_group(seq, group_key, cmp_key): - d = {} - for item in seq: - key = group_key(item) - if key in d: - v1 = cmp_key(item) - v2 = cmp_key(d[key]) - if v2 > v1: - d[key] = item - else: - d[key] = item - return list(d.values()) - -class Reconstructor: - def __init__(self, parser, term_subs={}): - # XXX TODO calling compile twice returns different results! - assert parser.options.maybe_placeholders == False - tokens, rules, _grammar_extra = parser.grammar.compile(parser.options.start) - - self.write_tokens = WriteTokensTransformer({t.name:t for t in tokens}, term_subs) - self.rules = list(self._build_recons_rules(rules)) - self.rules.reverse() - - # Choose the best rule from each group of {rule => [rule.alias]}, since we only really need one derivation. - self.rules = best_from_group(self.rules, lambda r: r, lambda r: -len(r.expansion)) - - self.rules.sort(key=lambda r: len(r.expansion)) - callbacks = {rule: rule.alias for rule in self.rules} # TODO pass callbacks through dict, instead of alias? - self.parser = earley.Parser(ParserConf(self.rules, callbacks, parser.options.start), - self._match, resolve_ambiguity=True) - - def _build_recons_rules(self, rules): - expand1s = {r.origin for r in rules if r.options.expand1} - - aliases = defaultdict(list) - for r in rules: - if r.alias: - aliases[r.origin].append( r.alias ) - - rule_names = {r.origin for r in rules} - nonterminals = {sym for sym in rule_names - if sym.name.startswith('_') or sym in expand1s or sym in aliases } - - for r in rules: - recons_exp = [sym if sym in nonterminals else Terminal(sym.name) - for sym in r.expansion if not is_discarded_terminal(sym)] - - # Skip self-recursive constructs - if recons_exp == [r.origin]: - continue - - sym = NonTerminal(r.alias) if r.alias else r.origin - - yield Rule(sym, recons_exp, alias=MakeMatchTree(sym.name, r.expansion)) - - for origin, rule_aliases in aliases.items(): - for alias in rule_aliases: - yield Rule(origin, [Terminal(alias)], alias=MakeMatchTree(origin.name, [NonTerminal(alias)])) - yield Rule(origin, [Terminal(origin.name)], alias=MakeMatchTree(origin.name, [origin])) - - def _match(self, term, token): - if isinstance(token, Tree): - return Terminal(token.data) == term - elif isinstance(token, Token): - return term == Terminal(token.type) - assert False +class Reconstructor(TreeMatcher): + """ + A Reconstructor that will, given a full parse Tree, generate source code. + + Note: + The reconstructor cannot generate values from regexps. If you need to produce discarded + regexes, such as newlines, use `term_subs` and provide default values for them. + + Paramters: + parser: a Lark instance + term_subs: a dictionary of [Terminal name as str] to [output text as str] + """ + + write_tokens: WriteTokensTransformer + + def __init__(self, parser: Lark, term_subs: Optional[Dict[str, Callable[[Symbol], str]]]=None) -> None: + TreeMatcher.__init__(self, parser) + + self.write_tokens = WriteTokensTransformer({t.name:t for t in self.tokens}, term_subs or {}) def _reconstruct(self, tree): - # TODO: ambiguity? - unreduced_tree = self.parser.parse(tree.children, tree.data) # find a full derivation - assert unreduced_tree.data == tree.data + unreduced_tree = self.match_tree(tree, tree.data) + res = self.write_tokens.transform(unreduced_tree) for item in res: if isinstance(item, Tree): - for x in self._reconstruct(item): - yield x + # TODO use orig_expansion.rulename to support templates + yield from self._reconstruct(item) else: yield item - def reconstruct(self, tree): + def reconstruct(self, tree: ParseTree, postproc: Optional[Callable[[Iterable[str]], Iterable[str]]]=None, insert_spaces: bool=True) -> str: x = self._reconstruct(tree) + if postproc: + x = postproc(x) y = [] prev_item = '' for item in x: - if prev_item and item and prev_item[-1].isalnum() and item[0].isalnum(): + if insert_spaces and prev_item and item and is_id_continue(prev_item[-1]) and is_id_continue(item[0]): y.append(' ') y.append(item) prev_item = item diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct2.py b/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct2.py deleted file mode 100644 index c7300a062..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/reconstruct2.py +++ /dev/null @@ -1,155 +0,0 @@ -from collections import defaultdict - -from .tree import Tree -from .visitors import Transformer_InPlace -from .common import ParserConf -from .lexer import Token, PatternStr -from .parsers import earley -from .grammar import Rule, Terminal, NonTerminal - - - -def is_discarded_terminal(t): - return t.is_term and t.filter_out - -def is_iter_empty(i): - try: - _ = next(i) - return False - except StopIteration: - return True - -class WriteTokensTransformer(Transformer_InPlace): - def __init__(self, tokens): - self.tokens = tokens - - def __default__(self, data, children, meta): - # if not isinstance(t, MatchTree): - # return t - if not getattr(meta, 'match_tree', False): - return Tree(data, children) - - iter_args = iter(children) - print('@@@', children, meta.orig_expansion) - to_write = [] - for sym in meta.orig_expansion: - if is_discarded_terminal(sym): - t = self.tokens[sym.name] - value = t.pattern.value - if not isinstance(t.pattern, PatternStr): - if t.name == "_NEWLINE": - value = "\n" - else: - raise NotImplementedError("Reconstructing regexps not supported yet: %s" % t) - to_write.append(value) - else: - x = next(iter_args) - if isinstance(x, list): - to_write += x - else: - if isinstance(x, Token): - assert Terminal(x.type) == sym, x - else: - assert NonTerminal(x.data) == sym, (sym, x) - to_write.append(x) - - assert is_iter_empty(iter_args) - return to_write - - -class MatchTree(Tree): - pass - -class MakeMatchTree: - def __init__(self, name, expansion): - self.name = name - self.expansion = expansion - - def __call__(self, args): - t = MatchTree(self.name, args) - t.meta.match_tree = True - t.meta.orig_expansion = self.expansion - return t - -from lark.load_grammar import SimplifyRule_Visitor, RuleTreeToText -class Reconstructor: - def __init__(self, parser): - # XXX TODO calling compile twice returns different results! - assert parser.options.maybe_placeholders == False - tokens, rules, _grammar_extra = parser.grammar.compile(parser.options.start) - - self.write_tokens = WriteTokensTransformer({t.name:t for t in tokens}) - self.rules = list(set(list(self._build_recons_rules(rules)))) - callbacks = {rule: rule.alias for rule in self.rules} # TODO pass callbacks through dict, instead of alias? - for r in self.rules: - print("*", r) - self.parser = earley.Parser(ParserConf(self.rules, callbacks, parser.options.start), - self._match, resolve_ambiguity=True) - - def _build_recons_rules(self, rules): - expand1s = {r.origin for r in rules if r.options.expand1} - - aliases = defaultdict(list) - for r in rules: - if r.alias: - aliases[r.origin].append( r.alias ) - - rule_names = {r.origin for r in rules} - nonterminals = {sym for sym in rule_names - if sym.name.startswith('_') or sym in expand1s or sym in aliases } - - for r in rules: - _recons_exp = [] - for sym in r.expansion: - if not is_discarded_terminal(sym): - if sym in nonterminals: - if sym in expand1s: - v = Tree('expansions', [sym, Terminal(sym.name.upper())]) - else: - v = sym - else: - v = Terminal(sym.name.upper()) - _recons_exp.append(v) - - simplify_rule = SimplifyRule_Visitor() - rule_tree_to_text = RuleTreeToText() - tree = Tree('expansions', [Tree('expansion', _recons_exp)]) - simplify_rule.visit(tree) - expansions = rule_tree_to_text.transform(tree) - - for recons_exp, alias in expansions: - - # Skip self-recursive constructs - if recons_exp == [r.origin]: - continue - - sym = NonTerminal(r.alias) if r.alias else r.origin - - yield Rule(sym, recons_exp, alias=MakeMatchTree(sym.name, r.expansion)) - - for origin, rule_aliases in aliases.items(): - for alias in rule_aliases: - yield Rule(origin, [Terminal(alias.upper())], alias=MakeMatchTree(origin.name, [NonTerminal(alias)])) - yield Rule(origin, [Terminal(origin.name.upper())], alias=MakeMatchTree(origin.name, [origin])) - - def _match(self, term, token): - if isinstance(token, Tree): - return Terminal(token.data.upper()) == term - elif isinstance(token, Token): - return term == Terminal(token.type.upper()) - assert False - - def _reconstruct(self, tree): - # TODO: ambiguity? - unreduced_tree = self.parser.parse(tree.children, tree.data) # find a full derivation - assert unreduced_tree.data == tree.data - res = self.write_tokens.transform(unreduced_tree) - for item in res: - if isinstance(item, Tree): - for x in self._reconstruct(item): - yield x - else: - yield item - - def reconstruct(self, tree): - return ''.join(self._reconstruct(tree)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/__init__.py index e69de29bb..391f991f1 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/__init__.py @@ -0,0 +1,64 @@ +import sys +from argparse import ArgumentParser, FileType +from textwrap import indent +from logging import DEBUG, INFO, WARN, ERROR +from typing import Optional +import warnings + +from lark import Lark, logger + +lalr_argparser = ArgumentParser(add_help=False, epilog='Look at the Lark documentation for more info on the options') + +flags = [ + ('d', 'debug'), + 'keep_all_tokens', + 'regex', + 'propagate_positions', + 'maybe_placeholders', + 'use_bytes' +] + +options = ['start', 'lexer'] + +lalr_argparser.add_argument('-v', '--verbose', action='count', default=0, help="Increase Logger output level, up to three times") +lalr_argparser.add_argument('-s', '--start', action='append', default=[]) +lalr_argparser.add_argument('-l', '--lexer', default='contextual', choices=('basic', 'contextual')) +encoding: Optional[str] = 'utf-8' if sys.version_info > (3, 4) else None +lalr_argparser.add_argument('-o', '--out', type=FileType('w', encoding=encoding), default=sys.stdout, help='the output file (default=stdout)') +lalr_argparser.add_argument('grammar_file', type=FileType('r', encoding=encoding), help='A valid .lark file') + +for flag in flags: + if isinstance(flag, tuple): + options.append(flag[1]) + lalr_argparser.add_argument('-' + flag[0], '--' + flag[1], action='store_true') + elif isinstance(flag, str): + options.append(flag) + lalr_argparser.add_argument('--' + flag, action='store_true') + else: + raise NotImplementedError("flags must only contain strings or tuples of strings") + + +def build_lalr(namespace): + logger.setLevel((ERROR, WARN, INFO, DEBUG)[min(namespace.verbose, 3)]) + if len(namespace.start) == 0: + namespace.start.append('start') + kwargs = {n: getattr(namespace, n) for n in options} + return Lark(namespace.grammar_file, parser='lalr', **kwargs), namespace.out + + +def showwarning_as_comment(message, category, filename, lineno, file=None, line=None): + # Based on warnings._showwarnmsg_impl + text = warnings.formatwarning(message, category, filename, lineno, line) + text = indent(text, '# ') + if file is None: + file = sys.stderr + if file is None: + return + try: + file.write(text) + except OSError: + pass + + +def make_warnings_comments(): + warnings.showwarning = showwarning_as_comment diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/nearley.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/nearley.py index 0b04fb55f..1fc27d565 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/nearley.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/nearley.py @@ -1,11 +1,12 @@ -"Converts between Lark and Nearley grammars. Work in progress!" +"Converts Nearley grammars to Lark" import os.path import sys import codecs +import argparse -from lark import Lark, InlineTransformer +from lark import Lark, Transformer, v_args nearley_grammar = r""" start: (ruledef|directive)+ @@ -34,20 +35,23 @@ COMMENT: /#[^\n]*/ REGEXP: /\[.*?\]/ - %import common.ESCAPED_STRING -> STRING + STRING: _STRING "i"? + + %import common.ESCAPED_STRING -> _STRING %import common.WS %ignore WS %ignore COMMENT """ -nearley_grammar_parser = Lark(nearley_grammar, parser='earley', lexer='standard') +nearley_grammar_parser = Lark(nearley_grammar, parser='earley', lexer='basic') def _get_rulename(name): - name = {'_': '_ws_maybe', '__':'_ws'}.get(name, name) + name = {'_': '_ws_maybe', '__': '_ws'}.get(name, name) return 'n_' + name.replace('$', '__DOLLAR__').lower() -class NearleyToLark(InlineTransformer): +@v_args(inline=True) +class NearleyToLark(Transformer): def __init__(self): self._count = 0 self.extra_rules = {} @@ -130,14 +134,14 @@ def _nearley_to_lark(g, builtin_path, n2l, js_code, folder_path, includes): elif statement.data == 'macro': pass # TODO Add support for macros! elif statement.data == 'ruledef': - rule_defs.append( n2l.transform(statement) ) + rule_defs.append(n2l.transform(statement)) else: raise Exception("Unknown statement: %s" % statement) return rule_defs -def create_code_for_nearley_grammar(g, start, builtin_path, folder_path): +def create_code_for_nearley_grammar(g, start, builtin_path, folder_path, es6=False): import js2py emit_code = [] @@ -160,7 +164,10 @@ def emit(x=None): for alias, code in n2l.alias_js_code.items(): js_code.append('%s = (%s);' % (alias, code)) - emit(js2py.translate_js('\n'.join(js_code))) + if es6: + emit(js2py.translate_js6('\n'.join(js_code))) + else: + emit(js2py.translate_js('\n'.join(js_code))) emit('class TransformNearley(Transformer):') for alias in n2l.alias_js_code: emit(" %s = var.get('%s').to_python()" % (alias, alias)) @@ -173,18 +180,23 @@ def emit(x=None): return ''.join(emit_code) -def main(fn, start, nearley_lib): +def main(fn, start, nearley_lib, es6=False): with codecs.open(fn, encoding='utf8') as f: grammar = f.read() - return create_code_for_nearley_grammar(grammar, start, os.path.join(nearley_lib, 'builtin'), os.path.abspath(os.path.dirname(fn))) + return create_code_for_nearley_grammar(grammar, start, os.path.join(nearley_lib, 'builtin'), os.path.abspath(os.path.dirname(fn)), es6=es6) +def get_arg_parser(): + parser = argparse.ArgumentParser(description='Reads a Nearley grammar (with js functions), and outputs an equivalent lark parser.') + parser.add_argument('nearley_grammar', help='Path to the file containing the nearley grammar') + parser.add_argument('start_rule', help='Rule within the nearley grammar to make the base rule') + parser.add_argument('nearley_lib', help='Path to root directory of nearley codebase (used for including builtins)') + parser.add_argument('--es6', help='Enable experimental ES6 support', action='store_true') + return parser if __name__ == '__main__': - if len(sys.argv) < 4: - print("Reads Nearley grammar (with js functions) outputs an equivalent lark parser.") - print("Usage: %s " % sys.argv[0]) + parser = get_arg_parser() + if len(sys.argv) == 1: + parser.print_help(sys.stderr) sys.exit(1) - - fn, start, nearley_lib = sys.argv[1:] - - print(main(fn, start, nearley_lib)) + args = parser.parse_args() + print(main(fn=args.nearley_grammar, start=args.start_rule, nearley_lib=args.nearley_lib, es6=args.es6)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/serialize.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/serialize.py index fb69d35ac..61540242a 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/serialize.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/serialize.py @@ -5,20 +5,16 @@ from lark import Lark from lark.grammar import RuleOptions, Rule from lark.lexer import TerminalDef +from lark.tools import lalr_argparser, build_lalr import argparse -argparser = argparse.ArgumentParser(prog='python -m lark.tools.serialize') #description='''Lark Serialization Tool -- Stores Lark's internal state & LALR analysis as a convenient JSON file''') +argparser = argparse.ArgumentParser(prog='python -m lark.tools.serialize', parents=[lalr_argparser], + description="Lark Serialization Tool - Stores Lark's internal state & LALR analysis as a JSON file", + epilog='Look at the Lark documentation for more info on the options') -argparser.add_argument('grammar_file', type=argparse.FileType('r'), help='A valid .lark file') -argparser.add_argument('-o', '--out', type=argparse.FileType('w'), default=sys.stdout, help='json file path to create (default=stdout)') -argparser.add_argument('-s', '--start', default='start', help='start symbol (default="start")', nargs='+') -argparser.add_argument('-l', '--lexer', default='standard', choices=['standard', 'contextual'], help='lexer type (default="standard")') - - -def serialize(infile, outfile, lexer, start): - lark_inst = Lark(infile, parser="lalr", lexer=lexer, start=start) # TODO contextual +def serialize(lark_inst, outfile): data, memo = lark_inst.memo_serialize([TerminalDef, Rule]) outfile.write('{\n') outfile.write(' "data": %s,\n' % json.dumps(data)) @@ -27,13 +23,12 @@ def serialize(infile, outfile, lexer, start): def main(): - if len(sys.argv) == 1 or '-h' in sys.argv or '--help' in sys.argv: - print("Lark Serialization Tool - Stores Lark's internal state & LALR analysis as a JSON file") - print("") - argparser.print_help() - else: - args = argparser.parse_args() - serialize(args.grammar_file, args.out, args.lexer, args.start) + if len(sys.argv)==1: + argparser.print_help(sys.stderr) + sys.exit(1) + ns = argparser.parse_args() + serialize(*build_lalr(ns)) + if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/standalone.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/standalone.py index 72042cdaa..3ae2cdb6d 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/tools/standalone.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tools/standalone.py @@ -3,7 +3,7 @@ # # Lark Stand-alone Generator Tool # ---------------------------------- -# Generates a stand-alone LALR(1) parser with a standard lexer +# Generates a stand-alone LALR(1) parser # # Git: https://github.com/erezsh/lark # Author: Erez Shinan (erezshin@gmail.com) @@ -24,23 +24,29 @@ # # -import os -from io import open +from abc import ABC, abstractmethod +from collections.abc import Sequence +from types import ModuleType +from typing import ( + TypeVar, Generic, Type, Tuple, List, Dict, Iterator, Collection, Callable, Optional, FrozenSet, Any, + Union, Iterable, IO, TYPE_CHECKING, overload, + Pattern as REPattern, ClassVar, Set, Mapping +) ###} -import codecs import sys +import token, tokenize import os -from pprint import pprint from os import path from collections import defaultdict +from functools import partial +from argparse import ArgumentParser import lark -from lark import Lark -from lark.parsers.lalr_analysis import Reduce +from lark.tools import lalr_argparser, build_lalr, make_warnings_comments -from lark.grammar import RuleOptions, Rule +from lark.grammar import Rule from lark.lexer import TerminalDef _dir = path.dirname(__file__) @@ -53,7 +59,6 @@ 'utils.py', 'tree.py', 'visitors.py', - 'indenter.py', 'grammar.py', 'lexer.py', 'common.py', @@ -62,66 +67,128 @@ 'parsers/lalr_analysis.py', 'parser_frontends.py', 'lark.py', + 'indenter.py', ] def extract_sections(lines): section = None text = [] sections = defaultdict(list) - for l in lines: - if l.startswith('###'): - if l[3] == '{': - section = l[4:].strip() - elif l[3] == '}': + for line in lines: + if line.startswith('###'): + if line[3] == '{': + section = line[4:].strip() + elif line[3] == '}': sections[section] += text section = None text = [] else: - raise ValueError(l) + raise ValueError(line) elif section: - text.append(l) + text.append(line) + + return {name: ''.join(text) for name, text in sections.items()} + + +def strip_docstrings(line_gen): + """ Strip comments and docstrings from a file. + Based on code from: https://stackoverflow.com/questions/1769332/script-to-remove-python-comments-docstrings + """ + res = [] + + prev_toktype = token.INDENT + last_lineno = -1 + last_col = 0 + + tokgen = tokenize.generate_tokens(line_gen) + for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in tokgen: + if slineno > last_lineno: + last_col = 0 + if scol > last_col: + res.append(" " * (scol - last_col)) + if toktype == token.STRING and prev_toktype == token.INDENT: + # Docstring + res.append("#--") + elif toktype == tokenize.COMMENT: + # Comment + res.append("##\n") + else: + res.append(ttext) + prev_toktype = toktype + last_col = ecol + last_lineno = elineno + + return ''.join(res) + + +def gen_standalone(lark_inst, output=None, out=sys.stdout, compress=False): + if output is None: + output = partial(print, file=out) + + import pickle, zlib, base64 + def compressed_output(obj): + s = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL) + c = zlib.compress(s) + output(repr(base64.b64encode(c))) + + def output_decompress(name): + output('%(name)s = pickle.loads(zlib.decompress(base64.b64decode(%(name)s)))' % locals()) + + output('# The file was automatically generated by Lark v%s' % lark.__version__) + output('__version__ = "%s"' % lark.__version__) + output() + + for i, pyfile in enumerate(EXTRACT_STANDALONE_FILES): + with open(os.path.join(_larkdir, pyfile)) as f: + code = extract_sections(f)['standalone'] + if i: # if not this file + code = strip_docstrings(partial(next, iter(code.splitlines(True)))) + output(code) - return {name:''.join(text) for name, text in sections.items()} + data, m = lark_inst.memo_serialize([TerminalDef, Rule]) + output('import pickle, zlib, base64') + if compress: + output('DATA = (') + compressed_output(data) + output(')') + output_decompress('DATA') + output('MEMO = (') + compressed_output(m) + output(')') + output_decompress('MEMO') + else: + output('DATA = (') + output(data) + output(')') + output('MEMO = (') + output(m) + output(')') -def main(fobj, start): - lark_inst = Lark(fobj, parser="lalr", lexer="contextual", start=start) + output('Shift = 0') + output('Reduce = 1') + output("def Lark_StandAlone(**kwargs):") + output(" return Lark._load_from_dict(DATA, MEMO, **kwargs)") - print('# The file was automatically generated by Lark v%s' % lark.__version__) - for pyfile in EXTRACT_STANDALONE_FILES: - with open(os.path.join(_larkdir, pyfile)) as f: - print (extract_sections(f)['standalone']) - data, m = lark_inst.memo_serialize([TerminalDef, Rule]) - print( 'DATA = (' ) - # pprint(data, width=160) - print(data) - print(')') - print( 'MEMO = (') - print(m) - print(')') +def main(): + make_warnings_comments() + parser = ArgumentParser(prog="prog='python -m lark.tools.standalone'", description="Lark Stand-alone Generator Tool", + parents=[lalr_argparser], epilog='Look at the Lark documentation for more info on the options') + parser.add_argument('-c', '--compress', action='store_true', default=0, help="Enable compression") + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + ns = parser.parse_args() - print('Shift = 0') - print('Reduce = 1') - print("def Lark_StandAlone(transformer=None, postlex=None):") - print(" return Lark._load_from_dict(DATA, MEMO, transformer=transformer, postlex=postlex)") + lark_inst, out = build_lalr(ns) + gen_standalone(lark_inst, out=out, compress=ns.compress) + ns.out.close() + ns.grammar_file.close() if __name__ == '__main__': - if len(sys.argv) < 2: - print("Lark Stand-alone Generator Tool") - print("Usage: python -m lark.tools.standalone []") - sys.exit(1) - - if len(sys.argv) == 3: - fn, start = sys.argv[1:] - elif len(sys.argv) == 2: - fn, start = sys.argv[1], 'start' - else: - assert False, sys.argv - - with codecs.open(fn, encoding='utf8') as f: - main(f, start) + main() diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tree.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tree.py index f9767e43b..7ad620f9c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/tree.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tree.py @@ -1,51 +1,120 @@ -try: - from future_builtins import filter -except ImportError: - pass - +import sys from copy import deepcopy -from collections import OrderedDict +from typing import List, Callable, Iterator, Union, Optional, Generic, TypeVar, Any, TYPE_CHECKING + +if TYPE_CHECKING: + from .lexer import TerminalDef, Token + import rich + if sys.version_info >= (3, 8): + from typing import Literal + else: + from typing_extensions import Literal ###{standalone +from collections import OrderedDict + class Meta: + + empty: bool + line: int + column: int + start_pos: int + end_line: int + end_column: int + end_pos: int + orig_expansion: 'List[TerminalDef]' + match_tree: bool + def __init__(self): self.empty = True -class Tree(object): - def __init__(self, data, children, meta=None): + +_Leaf_T = TypeVar("_Leaf_T") +Branch = Union[_Leaf_T, 'Tree[_Leaf_T]'] + + +class Tree(Generic[_Leaf_T]): + """The main tree class. + + Creates a new tree, and stores "data" and "children" in attributes of the same name. + Trees can be hashed and compared. + + Parameters: + data: The name of the rule or alias + children: List of matched sub-rules and terminals + meta: Line & Column numbers (if ``propagate_positions`` is enabled). + meta attributes: line, column, start_pos, end_line, end_column, end_pos + """ + + data: str + children: 'List[Branch[_Leaf_T]]' + + def __init__(self, data: str, children: 'List[Branch[_Leaf_T]]', meta: Optional[Meta]=None) -> None: self.data = data self.children = children self._meta = meta @property - def meta(self): + def meta(self) -> Meta: if self._meta is None: self._meta = Meta() return self._meta def __repr__(self): - return 'Tree(%s, %s)' % (self.data, self.children) + return 'Tree(%r, %r)' % (self.data, self.children) def _pretty_label(self): return self.data def _pretty(self, level, indent_str): if len(self.children) == 1 and not isinstance(self.children[0], Tree): - return [ indent_str*level, self._pretty_label(), '\t', '%s' % (self.children[0],), '\n'] + return [indent_str*level, self._pretty_label(), '\t', '%s' % (self.children[0],), '\n'] - l = [ indent_str*level, self._pretty_label(), '\n' ] + l = [indent_str*level, self._pretty_label(), '\n'] for n in self.children: if isinstance(n, Tree): l += n._pretty(level+1, indent_str) else: - l += [ indent_str*(level+1), '%s' % (n,), '\n' ] + l += [indent_str*(level+1), '%s' % (n,), '\n'] return l - def pretty(self, indent_str=' '): + def pretty(self, indent_str: str=' ') -> str: + """Returns an indented string representation of the tree. + + Great for debugging. + """ return ''.join(self._pretty(0, indent_str)) + def __rich__(self, parent:'rich.tree.Tree'=None) -> 'rich.tree.Tree': + """Returns a tree widget for the 'rich' library. + + Example: + :: + from rich import print + from lark import Tree + + tree = Tree('root', ['node1', 'node2']) + print(tree) + """ + return self._rich(parent) + + def _rich(self, parent): + if parent: + tree = parent.add(f'[bold]{self.data}[/bold]') + else: + import rich.tree + tree = rich.tree.Tree(self.data) + + for c in self.children: + if isinstance(c, Tree): + c._rich(tree) + else: + tree.add(f'[green]{c}[/green]') + + return tree + def __eq__(self, other): try: return self.data == other.data and self.children == other.children @@ -55,37 +124,68 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) - def __hash__(self): + def __hash__(self) -> int: return hash((self.data, tuple(self.children))) - def iter_subtrees(self): + def iter_subtrees(self) -> 'Iterator[Tree[_Leaf_T]]': + """Depth-first iteration. + + Iterates over all the subtrees, never returning to the same node twice (Lark's parse-tree is actually a DAG). + """ queue = [self] subtrees = OrderedDict() for subtree in queue: subtrees[id(subtree)] = subtree - queue += [c for c in reversed(subtree.children) + # Reason for type ignore https://github.com/python/mypy/issues/10999 + queue += [c for c in reversed(subtree.children) # type: ignore[misc] if isinstance(c, Tree) and id(c) not in subtrees] del queue return reversed(list(subtrees.values())) - def find_pred(self, pred): - "Find all nodes where pred(tree) == True" + def iter_subtrees_topdown(self): + """Breadth-first iteration. + + Iterates over all the subtrees, return nodes in order like pretty() does. + """ + stack = [self] + while stack: + node = stack.pop() + if not isinstance(node, Tree): + continue + yield node + for child in reversed(node.children): + stack.append(child) + + def find_pred(self, pred: 'Callable[[Tree[_Leaf_T]], bool]') -> 'Iterator[Tree[_Leaf_T]]': + """Returns all nodes of the tree that evaluate pred(node) as true.""" return filter(pred, self.iter_subtrees()) - def find_data(self, data): - "Find all nodes where tree.data == data" + def find_data(self, data: str) -> 'Iterator[Tree[_Leaf_T]]': + """Returns all nodes of the tree whose data equals the given data.""" return self.find_pred(lambda t: t.data == data) ###} - def expand_kids_by_index(self, *indices): - "Expand (inline) children at the given indices" - for i in sorted(indices, reverse=True): # reverse so that changing tail won't affect indices - kid = self.children[i] - self.children[i:i+1] = kid.children + def expand_kids_by_data(self, *data_values): + """Expand (inline) children with any of the given data values. Returns True if anything changed""" + changed = False + for i in range(len(self.children)-1, -1, -1): + child = self.children[i] + if isinstance(child, Tree) and child.data in data_values: + self.children[i:i+1] = child.children + changed = True + return changed + + + def scan_values(self, pred: 'Callable[[Branch[_Leaf_T]], bool]') -> Iterator[_Leaf_T]: + """Return all values in the tree that evaluate pred(value) as true. - def scan_values(self, pred): + This can be used to find all the tokens in the tree. + + Example: + >>> all_tokens = tree.scan_values(lambda v: isinstance(v, Token)) + """ for c in self.children: if isinstance(c, Tree): for t in c.scan_values(pred): @@ -94,46 +194,35 @@ def scan_values(self, pred): if pred(c): yield c - def iter_subtrees_topdown(self): - stack = [self] - while stack: - node = stack.pop() - if not isinstance(node, Tree): - continue - yield node - for n in reversed(node.children): - stack.append(n) - def __deepcopy__(self, memo): return type(self)(self.data, deepcopy(self.children, memo), meta=self._meta) - def copy(self): + def copy(self) -> 'Tree[_Leaf_T]': return type(self)(self.data, self.children) - def set(self, data, children): + def set(self, data: str, children: 'List[Branch[_Leaf_T]]') -> None: self.data = data self.children = children - # XXX Deprecated! Here for backwards compatibility <0.6.0 - @property - def line(self): - return self.meta.line - @property - def column(self): - return self.meta.column - @property - def end_line(self): - return self.meta.end_line - @property - def end_column(self): - return self.meta.end_column + +ParseTree = Tree['Token'] class SlottedTree(Tree): __slots__ = 'data', 'children', 'rule', '_meta' -def pydot__tree_to_png(tree, filename, rankdir="LR", **kwargs): +def pydot__tree_to_png(tree: Tree, filename: str, rankdir: 'Literal["TB", "LR", "BT", "RL"]'="LR", **kwargs) -> None: + graph = pydot__tree_to_graph(tree, rankdir, **kwargs) + graph.write_png(filename) + + +def pydot__tree_to_dot(tree: Tree, filename, rankdir="LR", **kwargs): + graph = pydot__tree_to_graph(tree, rankdir, **kwargs) + graph.write(filename) + + +def pydot__tree_to_graph(tree: Tree, rankdir="LR", **kwargs): """Creates a colorful image that represents the tree (data+children, without meta) Possible values for `rankdir` are "TB", "LR", "BT", "RL", corresponding to @@ -144,7 +233,7 @@ def pydot__tree_to_png(tree, filename, rankdir="LR", **kwargs): possible attributes, see https://www.graphviz.org/doc/info/attrs.html. """ - import pydot + import pydot # type: ignore[import] graph = pydot.Dot(graph_type='digraph', rankdir=rankdir, **kwargs) i = [0] @@ -161,7 +250,7 @@ def _to_pydot(subtree): subnodes = [_to_pydot(child) if isinstance(child, Tree) else new_leaf(child) for child in subtree.children] - node = pydot.Node(i[0], style="filled", fillcolor="#%x"%color, label=subtree.data) + node = pydot.Node(i[0], style="filled", fillcolor="#%x" % color, label=subtree.data) i[0] += 1 graph.add_node(node) @@ -171,5 +260,4 @@ def _to_pydot(subtree): return node _to_pydot(tree) - graph.write_png(filename) - + return graph diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tree_matcher.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tree_matcher.py new file mode 100644 index 000000000..fdcd2bfc2 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tree_matcher.py @@ -0,0 +1,186 @@ +"""Tree matcher based on Lark grammar""" + +import re +from collections import defaultdict + +from . import Tree, Token +from .common import ParserConf +from .parsers import earley +from .grammar import Rule, Terminal, NonTerminal + + +def is_discarded_terminal(t): + return t.is_term and t.filter_out + + +class _MakeTreeMatch: + def __init__(self, name, expansion): + self.name = name + self.expansion = expansion + + def __call__(self, args): + t = Tree(self.name, args) + t.meta.match_tree = True + t.meta.orig_expansion = self.expansion + return t + + +def _best_from_group(seq, group_key, cmp_key): + d = {} + for item in seq: + key = group_key(item) + if key in d: + v1 = cmp_key(item) + v2 = cmp_key(d[key]) + if v2 > v1: + d[key] = item + else: + d[key] = item + return list(d.values()) + + +def _best_rules_from_group(rules): + rules = _best_from_group(rules, lambda r: r, lambda r: -len(r.expansion)) + rules.sort(key=lambda r: len(r.expansion)) + return rules + + +def _match(term, token): + if isinstance(token, Tree): + name, _args = parse_rulename(term.name) + return token.data == name + elif isinstance(token, Token): + return term == Terminal(token.type) + assert False, (term, token) + + +def make_recons_rule(origin, expansion, old_expansion): + return Rule(origin, expansion, alias=_MakeTreeMatch(origin.name, old_expansion)) + + +def make_recons_rule_to_term(origin, term): + return make_recons_rule(origin, [Terminal(term.name)], [term]) + + +def parse_rulename(s): + "Parse rule names that may contain a template syntax (like rule{a, b, ...})" + name, args_str = re.match(r'(\w+)(?:{(.+)})?', s).groups() + args = args_str and [a.strip() for a in args_str.split(',')] + return name, args + + + +class ChildrenLexer: + def __init__(self, children): + self.children = children + + def lex(self, parser_state): + return self.children + +class TreeMatcher: + """Match the elements of a tree node, based on an ontology + provided by a Lark grammar. + + Supports templates and inlined rules (`rule{a, b,..}` and `_rule`) + + Initiialize with an instance of Lark. + """ + + def __init__(self, parser): + # XXX TODO calling compile twice returns different results! + assert not parser.options.maybe_placeholders + # XXX TODO: we just ignore the potential existence of a postlexer + self.tokens, rules, _extra = parser.grammar.compile(parser.options.start, set()) + + self.rules_for_root = defaultdict(list) + + self.rules = list(self._build_recons_rules(rules)) + self.rules.reverse() + + # Choose the best rule from each group of {rule => [rule.alias]}, since we only really need one derivation. + self.rules = _best_rules_from_group(self.rules) + + self.parser = parser + self._parser_cache = {} + + def _build_recons_rules(self, rules): + "Convert tree-parsing/construction rules to tree-matching rules" + expand1s = {r.origin for r in rules if r.options.expand1} + + aliases = defaultdict(list) + for r in rules: + if r.alias: + aliases[r.origin].append(r.alias) + + rule_names = {r.origin for r in rules} + nonterminals = {sym for sym in rule_names + if sym.name.startswith('_') or sym in expand1s or sym in aliases} + + seen = set() + for r in rules: + recons_exp = [sym if sym in nonterminals else Terminal(sym.name) + for sym in r.expansion if not is_discarded_terminal(sym)] + + # Skip self-recursive constructs + if recons_exp == [r.origin] and r.alias is None: + continue + + sym = NonTerminal(r.alias) if r.alias else r.origin + rule = make_recons_rule(sym, recons_exp, r.expansion) + + if sym in expand1s and len(recons_exp) != 1: + self.rules_for_root[sym.name].append(rule) + + if sym.name not in seen: + yield make_recons_rule_to_term(sym, sym) + seen.add(sym.name) + else: + if sym.name.startswith('_') or sym in expand1s: + yield rule + else: + self.rules_for_root[sym.name].append(rule) + + for origin, rule_aliases in aliases.items(): + for alias in rule_aliases: + yield make_recons_rule_to_term(origin, NonTerminal(alias)) + yield make_recons_rule_to_term(origin, origin) + + def match_tree(self, tree, rulename): + """Match the elements of `tree` to the symbols of rule `rulename`. + + Parameters: + tree (Tree): the tree node to match + rulename (str): The expected full rule name (including template args) + + Returns: + Tree: an unreduced tree that matches `rulename` + + Raises: + UnexpectedToken: If no match was found. + + Note: + It's the callers' responsibility match the tree recursively. + """ + if rulename: + # validate + name, _args = parse_rulename(rulename) + assert tree.data == name + else: + rulename = tree.data + + # TODO: ambiguity? + try: + parser = self._parser_cache[rulename] + except KeyError: + rules = self.rules + _best_rules_from_group(self.rules_for_root[rulename]) + + # TODO pass callbacks through dict, instead of alias? + callbacks = {rule: rule.alias for rule in rules} + conf = ParserConf(rules, callbacks, [rulename]) + parser = earley.Parser(self.parser.lexer_conf, conf, _match, resolve_ambiguity=True) + self._parser_cache[rulename] = parser + + # find a full derivation + unreduced_tree = parser.parse(ChildrenLexer(tree.children), rulename) + assert unreduced_tree.data == rulename + return unreduced_tree diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/tree_templates.py b/conda_lock/_vendor/poetry/core/_vendor/lark/tree_templates.py new file mode 100644 index 000000000..03eaa27b8 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/tree_templates.py @@ -0,0 +1,180 @@ +"""This module defines utilities for matching and translation tree templates. + +A tree templates is a tree that contains nodes that are template variables. + +""" + +from typing import Union, Optional, Mapping, Dict, Tuple, Iterator + +from lark import Tree, Transformer +from lark.exceptions import MissingVariableError + +Branch = Union[Tree[str], str] +TreeOrCode = Union[Tree[str], str] +MatchResult = Dict[str, Tree] +_TEMPLATE_MARKER = '$' + + +class TemplateConf: + """Template Configuration + + Allows customization for different uses of Template + + parse() must return a Tree instance. + """ + + def __init__(self, parse=None): + self._parse = parse + + def test_var(self, var: Union[Tree[str], str]) -> Optional[str]: + """Given a tree node, if it is a template variable return its name. Otherwise, return None. + + This method may be overridden for customization + + Parameters: + var: Tree | str - The tree node to test + + """ + if isinstance(var, str): + return _get_template_name(var) + + if ( + isinstance(var, Tree) + and var.data == "var" + and len(var.children) > 0 + and isinstance(var.children[0], str) + ): + return _get_template_name(var.children[0]) + + return None + + def _get_tree(self, template: TreeOrCode) -> Tree[str]: + if isinstance(template, str): + assert self._parse + template = self._parse(template) + + if not isinstance(template, Tree): + raise TypeError("template parser must return a Tree instance") + + return template + + def __call__(self, template: Tree[str]) -> 'Template': + return Template(template, conf=self) + + def _match_tree_template(self, template: TreeOrCode, tree: Branch) -> Optional[MatchResult]: + """Returns dict of {var: match} if found a match, else None + """ + template_var = self.test_var(template) + if template_var: + if not isinstance(tree, Tree): + raise TypeError(f"Template variables can only match Tree instances. Not {tree!r}") + return {template_var: tree} + + if isinstance(template, str): + if template == tree: + return {} + return None + + assert isinstance(template, Tree) and isinstance(tree, Tree), f"template={template} tree={tree}" + + if template.data == tree.data and len(template.children) == len(tree.children): + res = {} + for t1, t2 in zip(template.children, tree.children): + matches = self._match_tree_template(t1, t2) + if matches is None: + return None + + res.update(matches) + + return res + + return None + + +class _ReplaceVars(Transformer[str, Tree[str]]): + def __init__(self, conf: TemplateConf, vars: Mapping[str, Tree[str]]) -> None: + super().__init__() + self._conf = conf + self._vars = vars + + def __default__(self, data, children, meta) -> Tree[str]: + tree = super().__default__(data, children, meta) + + var = self._conf.test_var(tree) + if var: + try: + return self._vars[var] + except KeyError: + raise MissingVariableError(f"No mapping for template variable ({var})") + return tree + + +class Template: + """Represents a tree template, tied to a specific configuration + + A tree template is a tree that contains nodes that are template variables. + Those variables will match any tree. + (future versions may support annotations on the variables, to allow more complex templates) + """ + + def __init__(self, tree: Tree[str], conf: TemplateConf = TemplateConf()): + self.conf = conf + self.tree = conf._get_tree(tree) + + def match(self, tree: TreeOrCode) -> Optional[MatchResult]: + """Match a tree template to a tree. + + A tree template without variables will only match ``tree`` if it is equal to the template. + + Parameters: + tree (Tree): The tree to match to the template + + Returns: + Optional[Dict[str, Tree]]: If match is found, returns a dictionary mapping + template variable names to their matching tree nodes. + If no match was found, returns None. + """ + tree = self.conf._get_tree(tree) + return self.conf._match_tree_template(self.tree, tree) + + def search(self, tree: TreeOrCode) -> Iterator[Tuple[Tree[str], MatchResult]]: + """Search for all occurances of the tree template inside ``tree``. + """ + tree = self.conf._get_tree(tree) + for subtree in tree.iter_subtrees(): + res = self.match(subtree) + if res: + yield subtree, res + + def apply_vars(self, vars: Mapping[str, Tree[str]]) -> Tree[str]: + """Apply vars to the template tree + """ + return _ReplaceVars(self.conf, vars).transform(self.tree) + + +def translate(t1: Template, t2: Template, tree: TreeOrCode): + """Search tree and translate each occurrance of t1 into t2. + """ + tree = t1.conf._get_tree(tree) # ensure it's a tree, parse if necessary and possible + for subtree, vars in t1.search(tree): + res = t2.apply_vars(vars) + subtree.set(res.data, res.children) + return tree + + +class TemplateTranslator: + """Utility class for translating a collection of patterns + """ + + def __init__(self, translations: Mapping[Template, Template]): + assert all(isinstance(k, Template) and isinstance(v, Template) for k, v in translations.items()) + self.translations = translations + + def translate(self, tree: Tree[str]): + for k, v in self.translations.items(): + tree = translate(k, v, tree) + return tree + + +def _get_template_name(value: str) -> Optional[str]: + return value.lstrip(_TEMPLATE_MARKER) if value.startswith(_TEMPLATE_MARKER) else None diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/utils.py b/conda_lock/_vendor/poetry/core/_vendor/lark/utils.py index 36f50d1e4..6781e6fb1 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/utils.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/utils.py @@ -1,56 +1,27 @@ -import sys +import unicodedata import os from functools import reduce -from ast import literal_eval from collections import deque +from typing import Callable, Iterator, List, Optional, Tuple, Type, TypeVar, Union, Dict, Any, Sequence -class fzset(frozenset): - def __repr__(self): - return '{%s}' % ', '.join(map(repr, self)) +###{standalone +import sys, re +import logging +logger: logging.Logger = logging.getLogger("lark") +logger.addHandler(logging.StreamHandler()) +# Set to highest level, since we have some warnings amongst the code +# By default, we should not output any log messages +logger.setLevel(logging.CRITICAL) -def classify_bool(seq, pred): - true_elems = [] - false_elems = [] - - for elem in seq: - if pred(elem): - true_elems.append(elem) - else: - false_elems.append(elem) - - return true_elems, false_elems +NO_VALUE = object() +T = TypeVar("T") -def bfs(initial, expand): - open_q = deque(list(initial)) - visited = set(open_q) - while open_q: - node = open_q.popleft() - yield node - for next_node in expand(node): - if next_node not in visited: - visited.add(next_node) - open_q.append(next_node) - - - - -def _serialize(value, memo): - if isinstance(value, Serialize): - return value.serialize(memo) - elif isinstance(value, list): - return [_serialize(elem, memo) for elem in value] - elif isinstance(value, frozenset): - return list(value) # TODO reversible? - elif isinstance(value, dict): - return {key:_serialize(elem, memo) for key, elem in value.items()} - return value -###{standalone -def classify(seq, key=None, value=None): - d = {} +def classify(seq: Sequence, key: Optional[Callable] = None, value: Optional[Callable] = None) -> Dict: + d: Dict[Any, Any] = {} for item in seq: k = key(item) if (key is not None) else item v = value(item) if (value is not None) else item @@ -61,9 +32,9 @@ def classify(seq, key=None, value=None): return d -def _deserialize(data, namespace, memo): +def _deserialize(data: Any, namespace: Dict[str, Any], memo: Dict) -> Any: if isinstance(data, dict): - if '__type__' in data: # Object + if '__type__' in data: # Object class_ = namespace[data['__type__']] return class_.deserialize(data, memo) elif '@' in data: @@ -74,26 +45,35 @@ def _deserialize(data, namespace, memo): return data -class Serialize(object): - def memo_serialize(self, types_to_memoize): +_T = TypeVar("_T", bound="Serialize") + +class Serialize: + """Safe-ish serialization interface that doesn't rely on Pickle + + Attributes: + __serialize_fields__ (List[str]): Fields (aka attributes) to serialize. + __serialize_namespace__ (list): List of classes that deserialization is allowed to instantiate. + Should include all field types that aren't builtin types. + """ + + def memo_serialize(self, types_to_memoize: List) -> Any: memo = SerializeMemoizer(types_to_memoize) return self.serialize(memo), memo.serialize() - def serialize(self, memo=None): + def serialize(self, memo = None) -> Dict[str, Any]: if memo and memo.in_types(self): return {'@': memo.memoized.get(self)} fields = getattr(self, '__serialize_fields__') res = {f: _serialize(getattr(self, f), memo) for f in fields} res['__type__'] = type(self).__name__ - postprocess = getattr(self, '_serialize', None) - if postprocess: - postprocess(res, memo) + if hasattr(self, '_serialize'): + self._serialize(res, memo) # type: ignore[attr-defined] return res @classmethod - def deserialize(cls, data, memo): - namespace = getattr(cls, '__serialize_namespace__', {}) + def deserialize(cls: Type[_T], data: Dict[str, Any], memo: Dict[int, Any]) -> _T: + namespace = getattr(cls, '__serialize_namespace__', []) namespace = {c.__name__:c for c in namespace} fields = getattr(cls, '__serialize_fields__') @@ -107,77 +87,50 @@ def deserialize(cls, data, memo): setattr(inst, f, _deserialize(data[f], namespace, memo)) except KeyError as e: raise KeyError("Cannot find key for class", cls, e) - postprocess = getattr(inst, '_deserialize', None) - if postprocess: - postprocess() + + if hasattr(inst, '_deserialize'): + inst._deserialize() # type: ignore[attr-defined] + return inst class SerializeMemoizer(Serialize): + "A version of serialize that memoizes objects to reduce space" + __serialize_fields__ = 'memoized', - def __init__(self, types_to_memoize): + def __init__(self, types_to_memoize: List) -> None: self.types_to_memoize = tuple(types_to_memoize) self.memoized = Enumerator() - def in_types(self, value): + def in_types(self, value: Serialize) -> bool: return isinstance(value, self.types_to_memoize) - def serialize(self): + def serialize(self) -> Dict[int, Any]: # type: ignore[override] return _serialize(self.memoized.reversed(), None) @classmethod - def deserialize(cls, data, namespace, memo): + def deserialize(cls, data: Dict[int, Any], namespace: Dict[str, Any], memo: Dict[Any, Any]) -> Dict[int, Any]: # type: ignore[override] return _deserialize(data, namespace, memo) - -try: - STRING_TYPE = basestring -except NameError: # Python 3 - STRING_TYPE = str - - -import types -from functools import wraps, partial -from contextlib import contextmanager - -Str = type(u'') -try: - classtype = types.ClassType # Python2 -except AttributeError: - classtype = type # Python3 - -def smart_decorator(f, create_decorator): - if isinstance(f, types.FunctionType): - return wraps(f)(create_decorator(f, True)) - - elif isinstance(f, (classtype, type, types.BuiltinFunctionType)): - return wraps(f)(create_decorator(f, False)) - - elif isinstance(f, types.MethodType): - return wraps(f)(create_decorator(f.__func__, True)) - - elif isinstance(f, partial): - # wraps does not work for partials in 2.7: https://bugs.python.org/issue3445 - return wraps(f.func)(create_decorator(lambda *args, **kw: f(*args[1:], **kw), True)) - - else: - return create_decorator(f.__func__.__call__, True) - try: import regex + _has_regex = True except ImportError: - regex = None + _has_regex = False -import sys, re -Py36 = (sys.version_info[:2] >= (3, 6)) +if sys.version_info >= (3, 11): + import re._parser as sre_parse + import re._constants as sre_constants +else: + import sre_parse + import sre_constants -import sre_parse -import sre_constants categ_pattern = re.compile(r'\\p{[A-Za-z_]+}') -def get_regexp_width(expr): - if regex: + +def get_regexp_width(expr: str) -> Union[Tuple[int, int], List[int]]: + if _has_regex: # Since `sre_parse` cannot deal with Unicode categories of the form `\p{Mn}`, we replace these with # a simple letter, which makes no difference as we are only trying to get the possible lengths of the regex # match here below. @@ -187,61 +140,63 @@ def get_regexp_width(expr): raise ImportError('`regex` module must be installed in order to use Unicode categories.', expr) regexp_final = expr try: - return [int(x) for x in sre_parse.parse(regexp_final).getwidth()] + # Fixed in next version (past 0.960) of typeshed + return [int(x) for x in sre_parse.parse(regexp_final).getwidth()] # type: ignore[attr-defined] except sre_constants.error: - raise ValueError(expr) + if not _has_regex: + raise ValueError(expr) + else: + # sre_parse does not support the new features in regex. To not completely fail in that case, + # we manually test for the most important info (whether the empty string is matched) + c = regex.compile(regexp_final) + if c.match('') is None: + # MAXREPEAT is a none pickable subclass of int, therefore needs to be converted to enable caching + return 1, int(sre_constants.MAXREPEAT) + else: + return 0, int(sre_constants.MAXREPEAT) ###} -def dedup_list(l): - """Given a list (l) will removing duplicates from the list, - preserving the original order of the list. Assumes that - the list entries are hashable.""" - dedup = set() - return [ x for x in l if not (x in dedup or dedup.add(x))] - - - - -try: - from contextlib import suppress # Python 3 -except ImportError: - @contextmanager - def suppress(*excs): - '''Catch and dismiss the provided exception - - >>> x = 'hello' - >>> with suppress(IndexError): - ... x = x[10] - >>> x - 'hello' - ''' - try: - yield - except excs: - pass +_ID_START = 'Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Mn', 'Mc', 'Pc' +_ID_CONTINUE = _ID_START + ('Nd', 'Nl',) +def _test_unicode_category(s: str, categories: Sequence[str]) -> bool: + if len(s) != 1: + return all(_test_unicode_category(char, categories) for char in s) + return s == '_' or unicodedata.category(s) in categories +def is_id_continue(s: str) -> bool: + """ + Checks if all characters in `s` are alphanumeric characters (Unicode standard, so diacritics, indian vowels, non-latin + numbers, etc. all pass). Synonymous with a Python `ID_CONTINUE` identifier. See PEP 3131 for details. + """ + return _test_unicode_category(s, _ID_CONTINUE) +def is_id_start(s: str) -> bool: + """ + Checks if all characters in `s` are alphabetic characters (Unicode standard, so diacritics, indian vowels, non-latin + numbers, etc. all pass). Synonymous with a Python `ID_START` identifier. See PEP 3131 for details. + """ + return _test_unicode_category(s, _ID_START) -try: - compare = cmp -except NameError: - def compare(a, b): - if a == b: - return 0 - elif a > b: - return 1 - return -1 +def dedup_list(l: List[T]) -> List[T]: + """Given a list (l) will removing duplicates from the list, + preserving the original order of the list. Assumes that + the list entries are hashable.""" + dedup = set() + # This returns None, but that's expected + return [x for x in l if not (x in dedup or dedup.add(x))] # type: ignore[func-returns-value] + # 2x faster (ordered in PyPy and CPython 3.6+, gaurenteed to be ordered in Python 3.7+) + # return list(dict.fromkeys(l)) class Enumerator(Serialize): - def __init__(self): - self.enums = {} + def __init__(self) -> None: + self.enums: Dict[Any, int] = {} - def get(self, item): + def get(self, item) -> int: if item not in self.enums: self.enums[item] = len(self.enums) return self.enums[item] @@ -249,37 +204,12 @@ def get(self, item): def __len__(self): return len(self.enums) - def reversed(self): + def reversed(self) -> Dict[int, Any]: r = {v: k for k, v in self.enums.items()} assert len(r) == len(self.enums) return r -def eval_escaping(s): - w = '' - i = iter(s) - for n in i: - w += n - if n == '\\': - try: - n2 = next(i) - except StopIteration: - raise ValueError("Literal ended unexpectedly (bad escaping): `%r`" % s) - if n2 == '\\': - w += '\\\\' - elif n2 not in 'uxnftr': - w += '\\' - w += n2 - w = w.replace('\\"', '"').replace("'", "\\'") - - to_eval = "u'''%s'''" % w - try: - s = literal_eval(to_eval) - except SyntaxError as e: - raise ValueError(s, e) - - return s - def combine_alternatives(lists): """ @@ -302,7 +232,108 @@ def combine_alternatives(lists): return reduce(lambda a,b: [i+[j] for i in a for j in b], lists[1:], init) +try: + import atomicwrites + _has_atomicwrites = True +except ImportError: + _has_atomicwrites = False class FS: - open = open - exists = os.path.exists \ No newline at end of file + exists = staticmethod(os.path.exists) + + @staticmethod + def open(name, mode="r", **kwargs): + if _has_atomicwrites and "w" in mode: + return atomicwrites.atomic_write(name, mode=mode, overwrite=True, **kwargs) + else: + return open(name, mode, **kwargs) + + + +def isascii(s: str) -> bool: + """ str.isascii only exists in python3.7+ """ + if sys.version_info >= (3, 7): + return s.isascii() + else: + try: + s.encode('ascii') + return True + except (UnicodeDecodeError, UnicodeEncodeError): + return False + + +class fzset(frozenset): + def __repr__(self): + return '{%s}' % ', '.join(map(repr, self)) + + +def classify_bool(seq: Sequence, pred: Callable) -> Any: + true_elems = [] + false_elems = [] + + for elem in seq: + if pred(elem): + true_elems.append(elem) + else: + false_elems.append(elem) + + return true_elems, false_elems + + +def bfs(initial: Sequence, expand: Callable) -> Iterator: + open_q = deque(list(initial)) + visited = set(open_q) + while open_q: + node = open_q.popleft() + yield node + for next_node in expand(node): + if next_node not in visited: + visited.add(next_node) + open_q.append(next_node) + +def bfs_all_unique(initial, expand): + "bfs, but doesn't keep track of visited (aka seen), because there can be no repetitions" + open_q = deque(list(initial)) + while open_q: + node = open_q.popleft() + yield node + open_q += expand(node) + + +def _serialize(value: Any, memo: Optional[SerializeMemoizer]) -> Any: + if isinstance(value, Serialize): + return value.serialize(memo) + elif isinstance(value, list): + return [_serialize(elem, memo) for elem in value] + elif isinstance(value, frozenset): + return list(value) # TODO reversible? + elif isinstance(value, dict): + return {key:_serialize(elem, memo) for key, elem in value.items()} + # assert value is None or isinstance(value, (int, float, str, tuple)), value + return value + + + + +def small_factors(n: int, max_factor: int) -> List[Tuple[int, int]]: + """ + Splits n up into smaller factors and summands <= max_factor. + Returns a list of [(a, b), ...] + so that the following code returns n: + + n = 1 + for a, b in values: + n = n * a + b + + Currently, we also keep a + b <= max_factor, but that might change + """ + assert n >= 0 + assert max_factor > 2 + if n <= max_factor: + return [(n, 0)] + + for a in range(max_factor, 1, -1): + r, b = divmod(n, a) + if a + b <= max_factor: + return small_factors(r, max_factor) + [(a, b)] + assert False, "Failed to factorize %s" % n diff --git a/conda_lock/_vendor/poetry/core/_vendor/lark/visitors.py b/conda_lock/_vendor/poetry/core/_vendor/lark/visitors.py index c9f0e2dd3..932fbee19 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/lark/visitors.py +++ b/conda_lock/_vendor/poetry/core/_vendor/lark/visitors.py @@ -1,21 +1,54 @@ -from functools import wraps +from typing import TypeVar, Tuple, List, Callable, Generic, Type, Union, Optional, Any, cast +from abc import ABC -from .utils import smart_decorator, combine_alternatives -from .tree import Tree +from .utils import combine_alternatives +from .tree import Tree, Branch from .exceptions import VisitError, GrammarError from .lexer import Token ###{standalone +from functools import wraps, update_wrapper from inspect import getmembers, getmro -class Discard(Exception): - pass +_Return_T = TypeVar('_Return_T') +_Return_V = TypeVar('_Return_V') +_Leaf_T = TypeVar('_Leaf_T') +_Leaf_U = TypeVar('_Leaf_U') +_R = TypeVar('_R') +_FUNC = Callable[..., _Return_T] +_DECORATED = Union[_FUNC, type] + +class _DiscardType: + """When the Discard value is returned from a transformer callback, + that node is discarded and won't appear in the parent. + + Note: + This feature is disabled when the transformer is provided to Lark + using the ``transformer`` keyword (aka Tree-less LALR mode). + + Example: + :: + + class T(Transformer): + def ignore_tree(self, children): + return Discard + + def IGNORE_TOKEN(self, token): + return Discard + """ + + def __repr__(self): + return "lark.visitors.Discard" + +Discard = _DiscardType() # Transformers class _Decoratable: + "Provides support for decorating methods with @v_args" + @classmethod - def _apply_decorator(cls, decorator, **kwargs): + def _apply_v_args(cls, visit_wrapper): mro = getmro(cls) assert mro[0] is cls libmembers = {name for _cls in mro[1:] for name, _ in getmembers(_cls)} @@ -28,28 +61,51 @@ def _apply_decorator(cls, decorator, **kwargs): continue # Skip if v_args already applied (at the function level) - if hasattr(cls.__dict__[name], 'vargs_applied') or hasattr(value, 'vargs_applied'): + if isinstance(cls.__dict__[name], _VArgsWrapper): continue - static = isinstance(cls.__dict__[name], (staticmethod, classmethod)) - setattr(cls, name, decorator(value, static=static, **kwargs)) + setattr(cls, name, _VArgsWrapper(cls.__dict__[name], visit_wrapper)) return cls def __class_getitem__(cls, _): return cls -class Transformer(_Decoratable): - """Visits the tree recursively, starting with the leaves and finally the root (bottom-up) +class Transformer(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]): + """Transformers work bottom-up (or depth-first), starting with visiting the leaves and working + their way up until ending at the root of the tree. + + For each node visited, the transformer will call the appropriate method (callbacks), according to the + node's ``data``, and use the returned value to replace the node, thereby creating a new tree structure. + + Transformers can be used to implement map & reduce patterns. Because nodes are reduced from leaf to root, + at any point the callbacks may assume the children have already been transformed (if applicable). + + If the transformer cannot find a method with the right name, it will instead call ``__default__``, which by + default creates a copy of the node. + + To discard a node, return Discard (``lark.visitors.Discard``). - Calls its methods (provided by user via inheritance) according to tree.data - The returned value replaces the old one in the structure. + ``Transformer`` can do anything ``Visitor`` can do, but because it reconstructs the tree, + it is slightly less efficient. + + A transformer without methods essentially performs a non-memoized partial deepcopy. + + All these classes implement the transformer interface: + + - ``Transformer`` - Recursively transforms the tree. This is the one you probably want. + - ``Transformer_InPlace`` - Non-recursive. Changes the tree in-place instead of returning new instances + - ``Transformer_InPlaceRecursive`` - Recursive. Changes the tree in-place instead of returning new instances + + Parameters: + visit_tokens (bool, optional): Should the transformer visit tokens in addition to rules. + Setting this to ``False`` is slightly faster. Defaults to ``True``. + (For processing ignored tokens, use the ``lexer_callbacks`` options) - Can be used to implement map or reduce. """ __visit_tokens__ = True # For backwards compatibility - def __init__(self, visit_tokens=True): + def __init__(self, visit_tokens: bool=True) -> None: self.__visit_tokens__ = visit_tokens def _call_userfunc(self, tree, new_children=None): @@ -66,7 +122,7 @@ def _call_userfunc(self, tree, new_children=None): return f.visit_wrapper(f, tree.data, children, tree.meta) else: return f(children) - except (GrammarError, Discard): + except GrammarError: raise except Exception as e: raise VisitError(tree.data, tree, e) @@ -79,43 +135,106 @@ def _call_userfunc_token(self, token): else: try: return f(token) - except (GrammarError, Discard): + except GrammarError: raise except Exception as e: raise VisitError(token.type, token, e) - def _transform_children(self, children): for c in children: - try: - if isinstance(c, Tree): - yield self._transform_tree(c) - elif self.__visit_tokens__ and isinstance(c, Token): - yield self._call_userfunc_token(c) - else: - yield c - except Discard: - pass + if isinstance(c, Tree): + res = self._transform_tree(c) + elif self.__visit_tokens__ and isinstance(c, Token): + res = self._call_userfunc_token(c) + else: + res = c + + if res is not Discard: + yield res def _transform_tree(self, tree): children = list(self._transform_children(tree.children)) return self._call_userfunc(tree, children) - def transform(self, tree): + def transform(self, tree: Tree[_Leaf_T]) -> _Return_T: + "Transform the given tree, and return the final result" return self._transform_tree(tree) - def __mul__(self, other): + def __mul__( + self: 'Transformer[_Leaf_T, Tree[_Leaf_U]]', + other: 'Union[Transformer[_Leaf_U, _Return_V], TransformerChain[_Leaf_U, _Return_V,]]' + ) -> 'TransformerChain[_Leaf_T, _Return_V]': + """Chain two transformers together, returning a new transformer. + """ return TransformerChain(self, other) def __default__(self, data, children, meta): - "Default operation on tree (for override)" + """Default function that is called if there is no attribute matching ``data`` + + Can be overridden. Defaults to creating a new copy of the tree node (i.e. ``return Tree(data, children, meta)``) + """ return Tree(data, children, meta) def __default_token__(self, token): - "Default operation on token (for override)" + """Default function that is called if there is no attribute matching ``token.type`` + + Can be overridden. Defaults to returning the token as-is. + """ return token +def merge_transformers(base_transformer=None, **transformers_to_merge): + """Merge a collection of transformers into the base_transformer, each into its own 'namespace'. + + When called, it will collect the methods from each transformer, and assign them to base_transformer, + with their name prefixed with the given keyword, as ``prefix__methodname``. + + This function is especially useful for processing grammars that import other grammars, + thereby creating some of their rules in a 'namespace'. (i.e with a consistent name prefix). + In this case, the key for the transformer should match the name of the imported grammar. + + Parameters: + base_transformer (Transformer, optional): The transformer that all other transformers will be added to. + **transformers_to_merge: Keyword arguments, in the form of ``name_prefix = transformer``. + + Raises: + AttributeError: In case of a name collision in the merged methods + + Example: + :: + + class TBase(Transformer): + def start(self, children): + return children[0] + 'bar' + + class TImportedGrammar(Transformer): + def foo(self, children): + return "foo" + + composed_transformer = merge_transformers(TBase(), imported=TImportedGrammar()) + + t = Tree('start', [ Tree('imported__foo', []) ]) + + assert composed_transformer.transform(t) == 'foobar' + + """ + if base_transformer is None: + base_transformer = Transformer() + for prefix, transformer in transformers_to_merge.items(): + for method_name in dir(transformer): + method = getattr(transformer, method_name) + if not callable(method): + continue + if method_name.startswith("_") or method_name == "transform": + continue + prefixed_method = prefix + "__" + method_name + if hasattr(base_transformer, prefixed_method): + raise AttributeError("Cannot merge: method '%s' appears more than once" % prefixed_method) + + setattr(base_transformer, prefixed_method, method) + + return base_transformer + class InlineTransformer(Transformer): # XXX Deprecated def _call_userfunc(self, tree, new_children=None): @@ -129,25 +248,34 @@ def _call_userfunc(self, tree, new_children=None): return f(*children) -class TransformerChain(object): - def __init__(self, *transformers): +class TransformerChain(Generic[_Leaf_T, _Return_T]): + + transformers: 'Tuple[Union[Transformer, TransformerChain], ...]' + + def __init__(self, *transformers: 'Union[Transformer, TransformerChain]') -> None: self.transformers = transformers - def transform(self, tree): + def transform(self, tree: Tree[_Leaf_T]) -> _Return_T: for t in self.transformers: tree = t.transform(tree) - return tree + return cast(_Return_T, tree) - def __mul__(self, other): + def __mul__( + self: 'TransformerChain[_Leaf_T, Tree[_Leaf_U]]', + other: 'Union[Transformer[_Leaf_U, _Return_V], TransformerChain[_Leaf_U, _Return_V]]' + ) -> 'TransformerChain[_Leaf_T, _Return_V]': return TransformerChain(*self.transformers + (other,)) class Transformer_InPlace(Transformer): - "Non-recursive. Changes the tree in-place instead of returning new instances" + """Same as Transformer, but non-recursive, and changes the tree in-place instead of returning new instances + + Useful for huge trees. Conservative in memory. + """ def _transform_tree(self, tree): # Cancel recursion return self._call_userfunc(tree) - def transform(self, tree): + def transform(self, tree: Tree[_Leaf_T]) -> _Return_T: for subtree in tree.iter_subtrees(): subtree.children = list(self._transform_children(subtree.children)) @@ -155,20 +283,25 @@ def transform(self, tree): class Transformer_NonRecursive(Transformer): - "Non-recursive. Doesn't change the original tree." + """Same as Transformer but non-recursive. + + Like Transformer, it doesn't change the original tree. - def transform(self, tree): + Useful for huge trees. + """ + + def transform(self, tree: Tree[_Leaf_T]) -> _Return_T: # Tree to postfix rev_postfix = [] - q = [tree] + q: List[Branch[_Leaf_T]] = [tree] while q: t = q.pop() - rev_postfix.append( t ) + rev_postfix.append(t) if isinstance(t, Tree): q += t.children # Postfix to tree - stack = [] + stack: List = [] for x in reversed(rev_postfix): if isinstance(x, Tree): size = len(x.children) @@ -177,23 +310,32 @@ def transform(self, tree): del stack[-size:] else: args = [] - stack.append(self._call_userfunc(x, args)) + + res = self._call_userfunc(x, args) + if res is not Discard: + stack.append(res) + + elif self.__visit_tokens__ and isinstance(x, Token): + res = self._call_userfunc_token(x) + if res is not Discard: + stack.append(res) else: stack.append(x) - t ,= stack # We should have only one tree remaining - return t - + result, = stack # We should have only one tree remaining + # There are no guarantees on the type of the value produced by calling a user func for a + # child will produce. This means type system can't statically know that the final result is + # _Return_T. As a result a cast is required. + return cast(_Return_T, result) class Transformer_InPlaceRecursive(Transformer): - "Recursive. Changes the tree in-place instead of returning new instances" + "Same as Transformer, recursive, but changes the tree in-place instead of returning new instances" def _transform_tree(self, tree): tree.children = list(self._transform_children(tree.children)) return self._call_userfunc(tree) - # Visitors class VisitorBase: @@ -201,38 +343,45 @@ def _call_userfunc(self, tree): return getattr(self, tree.data, self.__default__)(tree) def __default__(self, tree): - "Default operation on tree (for override)" + """Default function that is called if there is no attribute matching ``tree.data`` + + Can be overridden. Defaults to doing nothing. + """ return tree def __class_getitem__(cls, _): return cls -class Visitor(VisitorBase): - """Bottom-up visitor, non-recursive +class Visitor(VisitorBase, ABC, Generic[_Leaf_T]): + """Tree visitor, non-recursive (can handle huge trees). - Visits the tree, starting with the leaves and finally the root (bottom-up) - Calls its methods (provided by user via inheritance) according to tree.data + Visiting a node calls its methods (provided by the user via inheritance) according to ``tree.data`` """ - def visit(self, tree): + def visit(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]: + "Visits the tree, starting with the leaves and finally the root (bottom-up)" for subtree in tree.iter_subtrees(): self._call_userfunc(subtree) return tree - def visit_topdown(self,tree): + def visit_topdown(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]: + "Visit the tree, starting at the root, and ending at the leaves (top-down)" for subtree in tree.iter_subtrees_topdown(): self._call_userfunc(subtree) return tree -class Visitor_Recursive(VisitorBase): - """Bottom-up visitor, recursive - Visits the tree, starting with the leaves and finally the root (bottom-up) - Calls its methods (provided by user via inheritance) according to tree.data +class Visitor_Recursive(VisitorBase, Generic[_Leaf_T]): + """Bottom-up visitor, recursive. + + Visiting a node calls its methods (provided by the user via inheritance) according to ``tree.data`` + + Slightly faster than the non-recursive version. """ - def visit(self, tree): + def visit(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]: + "Visits the tree, starting with the leaves and finally the root (bottom-up)" for child in tree.children: if isinstance(child, Tree): self.visit(child) @@ -240,7 +389,8 @@ def visit(self, tree): self._call_userfunc(tree) return tree - def visit_topdown(self,tree): + def visit_topdown(self,tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]: + "Visit the tree, starting at the root, and ending at the leaves (top-down)" self._call_userfunc(tree) for child in tree.children: @@ -250,27 +400,25 @@ def visit_topdown(self,tree): return tree - -def visit_children_decor(func): - "See Interpreter" - @wraps(func) - def inner(cls, tree): - values = cls.visit_children(tree) - return func(cls, values) - return inner - - -class Interpreter(_Decoratable): - """Top-down visitor, recursive +class Interpreter(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]): + """Interpreter walks the tree starting at the root. Visits the tree, starting with the root and finally the leaves (top-down) - Calls its methods (provided by user via inheritance) according to tree.data - Unlike Transformer and Visitor, the Interpreter doesn't automatically visit its sub-branches. - The user has to explicitly call visit_children, or use the @visit_children_decor + For each tree node, it calls its methods (provided by user via inheritance) according to ``tree.data``. + + Unlike ``Transformer`` and ``Visitor``, the Interpreter doesn't automatically visit its sub-branches. + The user has to explicitly call ``visit``, ``visit_children``, or use the ``@visit_children_decor``. + This allows the user to implement branching and loops. """ - def visit(self, tree): + def visit(self, tree: Tree[_Leaf_T]) -> _Return_T: + # There are no guarantees on the type of the value produced by calling a user func for a + # child will produce. So only annotate the public method and use an internal method when + # visiting child trees. + return self._visit_tree(tree) + + def _visit_tree(self, tree: Tree[_Leaf_T]): f = getattr(self, tree.data) wrapper = getattr(f, 'visit_wrapper', None) if wrapper is not None: @@ -278,8 +426,8 @@ def visit(self, tree): else: return f(tree) - def visit_children(self, tree): - return [self.visit(child) if isinstance(child, Tree) else child + def visit_children(self, tree: Tree[_Leaf_T]) -> List: + return [self._visit_tree(child) if isinstance(child, Tree) else child for child in tree.children] def __getattr__(self, name): @@ -289,69 +437,107 @@ def __default__(self, tree): return self.visit_children(tree) +_InterMethod = Callable[[Type[Interpreter], _Return_T], _R] +def visit_children_decor(func: _InterMethod) -> _InterMethod: + "See Interpreter" + @wraps(func) + def inner(cls, tree): + values = cls.visit_children(tree) + return func(cls, values) + return inner # Decorators -def _apply_decorator(obj, decorator, **kwargs): +def _apply_v_args(obj, visit_wrapper): try: - _apply = obj._apply_decorator + _apply = obj._apply_v_args except AttributeError: - return decorator(obj, **kwargs) + return _VArgsWrapper(obj, visit_wrapper) else: - return _apply(decorator, **kwargs) - + return _apply(visit_wrapper) -def _inline_args__func(func): - @wraps(func) - def create_decorator(_f, with_self): - if with_self: - def f(self, children): - return _f(self, *children) - else: - def f(self, children): - return _f(*children) - return f - - return smart_decorator(func, create_decorator) - - -def inline_args(obj): # XXX Deprecated - return _apply_decorator(obj, _inline_args__func) +class _VArgsWrapper: + """ + A wrapper around a Callable. It delegates `__call__` to the Callable. + If the Callable has a `__get__`, that is also delegate and the resulting function is wrapped. + Otherwise, we use the original function mirroring the behaviour without a __get__. + We also have the visit_wrapper attribute to be used by Transformers. + """ + base_func: Callable + def __init__(self, func: Callable, visit_wrapper: Callable[[Callable, str, list, Any], Any]): + if isinstance(func, _VArgsWrapper): + func = func.base_func + # https://github.com/python/mypy/issues/708 + self.base_func = func # type: ignore[assignment] + self.visit_wrapper = visit_wrapper + update_wrapper(self, func) + def __call__(self, *args, **kwargs): + return self.base_func(*args, **kwargs) -def _visitor_args_func_dec(func, visit_wrapper=None, static=False): - def create_decorator(_f, with_self): - if with_self: - def f(self, *args, **kwargs): - return _f(self, *args, **kwargs) + def __get__(self, instance, owner=None): + try: + # Use the __get__ attribute of the type instead of the instance + # to fully mirror the behavior of getattr + g = type(self.base_func).__get__ + except AttributeError: + return self else: - def f(self, *args, **kwargs): - return _f(*args, **kwargs) - return f + return _VArgsWrapper(g(self.base_func, instance, owner), self.visit_wrapper) - if static: - f = wraps(func)(create_decorator(func, False)) - else: - f = smart_decorator(func, create_decorator) - f.vargs_applied = True - f.visit_wrapper = visit_wrapper - return f + def __set_name__(self, owner, name): + try: + f = type(self.base_func).__set_name__ + except AttributeError: + return + else: + f(self.base_func, owner, name) -def _vargs_inline(f, data, children, meta): +def _vargs_inline(f, _data, children, _meta): return f(*children) -def _vargs_meta_inline(f, data, children, meta): +def _vargs_meta_inline(f, _data, children, meta): return f(meta, *children) -def _vargs_meta(f, data, children, meta): - return f(children, meta) # TODO swap these for consistency? Backwards incompatible! +def _vargs_meta(f, _data, children, meta): + return f(meta, children) def _vargs_tree(f, data, children, meta): return f(Tree(data, children, meta)) -def v_args(inline=False, meta=False, tree=False, wrapper=None): - "A convenience decorator factory, for modifying the behavior of user-supplied visitor methods" + +def v_args(inline: bool = False, meta: bool = False, tree: bool = False, wrapper: Optional[Callable] = None) -> Callable[[_DECORATED], _DECORATED]: + """A convenience decorator factory for modifying the behavior of user-supplied visitor methods. + + By default, callback methods of transformers/visitors accept one argument - a list of the node's children. + + ``v_args`` can modify this behavior. When used on a transformer/visitor class definition, + it applies to all the callback methods inside it. + + ``v_args`` can be applied to a single method, or to an entire class. When applied to both, + the options given to the method take precedence. + + Parameters: + inline (bool, optional): Children are provided as ``*args`` instead of a list argument (not recommended for very long lists). + meta (bool, optional): Provides two arguments: ``children`` and ``meta`` (instead of just the first) + tree (bool, optional): Provides the entire tree as the argument, instead of the children. + wrapper (function, optional): Provide a function to decorate all methods. + + Example: + :: + + @v_args(inline=True) + class SolveArith(Transformer): + def add(self, left, right): + return left + right + + + class ReverseNotation(Transformer_InPlace): + @v_args(tree=True) + def tree_node(self, tree): + tree.children = tree.children[::-1] + """ if tree and (meta or inline): raise ValueError("Visitor functions cannot combine 'tree' with 'meta' or 'inline'.") @@ -372,14 +558,14 @@ def v_args(inline=False, meta=False, tree=False, wrapper=None): func = wrapper def _visitor_args_dec(obj): - return _apply_decorator(obj, _visitor_args_func_dec, visit_wrapper=func) + return _apply_v_args(obj, func) return _visitor_args_dec ###} -#--- Visitor Utilities --- +# --- Visitor Utilities --- class CollapseAmbiguities(Transformer): """ @@ -393,7 +579,9 @@ class CollapseAmbiguities(Transformer): """ def _ambig(self, options): return sum(options, []) + def __default__(self, data, children_lists, meta): return [Tree(data, children, meta) for children in combine_alternatives(children_lists)] + def __default_token__(self, t): return [t] diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/__about__.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/__about__.py index 4c43a968c..3551bc2d2 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/__about__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/__about__.py @@ -1,7 +1,6 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function __all__ = [ "__title__", @@ -18,7 +17,7 @@ __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "20.9" +__version__ = "21.3" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/__init__.py index a0cf67df5..3c50c5dcf 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/__init__.py @@ -1,7 +1,6 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function from .__about__ import ( __author__, diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/_compat.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/_compat.py deleted file mode 100644 index e54bd4ede..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/_compat.py +++ /dev/null @@ -1,38 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import sys - -from ._typing import TYPE_CHECKING - -if TYPE_CHECKING: # pragma: no cover - from typing import Any, Dict, Tuple, Type - - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -# flake8: noqa - -if PY3: - string_types = (str,) -else: - string_types = (basestring,) - - -def with_metaclass(meta, *bases): - # type: (Type[Any], Tuple[Type[Any], ...]) -> Any - """ - Create a base class with a metaclass. - """ - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): # type: ignore - def __new__(cls, name, this_bases, d): - # type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any - return meta(name, bases, d) - - return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/_manylinux.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/_manylinux.py new file mode 100644 index 000000000..4c379aa6f --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/_manylinux.py @@ -0,0 +1,301 @@ +import collections +import functools +import os +import re +import struct +import sys +import warnings +from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple + + +# Python does not provide platform information at sufficient granularity to +# identify the architecture of the running executable in some cases, so we +# determine it dynamically by reading the information from the running +# process. This only applies on Linux, which uses the ELF format. +class _ELFFileHeader: + # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + class _InvalidELFFileHeader(ValueError): + """ + An invalid ELF file header was found. + """ + + ELF_MAGIC_NUMBER = 0x7F454C46 + ELFCLASS32 = 1 + ELFCLASS64 = 2 + ELFDATA2LSB = 1 + ELFDATA2MSB = 2 + EM_386 = 3 + EM_S390 = 22 + EM_ARM = 40 + EM_X86_64 = 62 + EF_ARM_ABIMASK = 0xFF000000 + EF_ARM_ABI_VER5 = 0x05000000 + EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + def __init__(self, file: IO[bytes]) -> None: + def unpack(fmt: str) -> int: + try: + data = file.read(struct.calcsize(fmt)) + result: Tuple[int, ...] = struct.unpack(fmt, data) + except struct.error: + raise _ELFFileHeader._InvalidELFFileHeader() + return result[0] + + self.e_ident_magic = unpack(">I") + if self.e_ident_magic != self.ELF_MAGIC_NUMBER: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_class = unpack("B") + if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_data = unpack("B") + if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: + raise _ELFFileHeader._InvalidELFFileHeader() + self.e_ident_version = unpack("B") + self.e_ident_osabi = unpack("B") + self.e_ident_abiversion = unpack("B") + self.e_ident_pad = file.read(7) + format_h = "H" + format_i = "I" + format_q = "Q" + format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q + self.e_type = unpack(format_h) + self.e_machine = unpack(format_h) + self.e_version = unpack(format_i) + self.e_entry = unpack(format_p) + self.e_phoff = unpack(format_p) + self.e_shoff = unpack(format_p) + self.e_flags = unpack(format_i) + self.e_ehsize = unpack(format_h) + self.e_phentsize = unpack(format_h) + self.e_phnum = unpack(format_h) + self.e_shentsize = unpack(format_h) + self.e_shnum = unpack(format_h) + self.e_shstrndx = unpack(format_h) + + +def _get_elf_header() -> Optional[_ELFFileHeader]: + try: + with open(sys.executable, "rb") as f: + elf_header = _ELFFileHeader(f) + except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): + return None + return elf_header + + +def _is_linux_armhf() -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_ARM + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABIMASK + ) == elf_header.EF_ARM_ABI_VER5 + result &= ( + elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD + ) == elf_header.EF_ARM_ABI_FLOAT_HARD + return result + + +def _is_linux_i686() -> bool: + elf_header = _get_elf_header() + if elf_header is None: + return False + result = elf_header.e_ident_class == elf_header.ELFCLASS32 + result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB + result &= elf_header.e_machine == elf_header.EM_386 + return result + + +def _have_compatible_abi(arch: str) -> bool: + if arch == "armv7l": + return _is_linux_armhf() + if arch == "i686": + return _is_linux_i686() + return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> Optional[str]: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". + version_string = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.split() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> Optional[str]: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> Optional[str]: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache() +def _get_glibc_version() -> Tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(name: str, arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux # noqa + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(linux: str, arch: str) -> Iterator[str]: + if not _have_compatible_abi(arch): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if arch in {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(tag, arch, glibc_version): + yield linux.replace("linux", tag) + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(legacy_tag, arch, glibc_version): + yield linux.replace("linux", legacy_tag) diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/_musllinux.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/_musllinux.py new file mode 100644 index 000000000..8ac3059ba --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/_musllinux.py @@ -0,0 +1,136 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +import contextlib +import functools +import operator +import os +import re +import struct +import subprocess +import sys +from typing import IO, Iterator, NamedTuple, Optional, Tuple + + +def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, f.read(struct.calcsize(fmt))) + + +def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]: + """Detect musl libc location by parsing the Python executable. + + Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca + ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + """ + f.seek(0) + try: + ident = _read_unpacked(f, "16B") + except struct.error: + return None + if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF. + return None + f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version. + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, p_fmt, p_idx = { + 1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit. + 2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit. + }[ident[4]] + except KeyError: + return None + else: + p_get = operator.itemgetter(*p_idx) + + # Find the interpreter section and return its content. + try: + _, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt) + except struct.error: + return None + for i in range(e_phnum + 1): + f.seek(e_phoff + e_phentsize * i) + try: + p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt)) + except struct.error: + return None + if p_type != 3: # Not PT_INTERP. + continue + f.seek(p_offset) + interpreter = os.fsdecode(f.read(p_filesz)).strip("\0") + if "musl" not in interpreter: + return None + return interpreter + return None + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> Optional[_MuslVersion]: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache() +def _get_musl_version(executable: str) -> Optional[_MuslVersion]: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + with contextlib.ExitStack() as stack: + try: + f = stack.enter_context(open(executable, "rb")) + except OSError: + return None + ld = _parse_ld_musl_from_elf(f) + if not ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(arch: str) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param arch: Should be the part of platform tag after the ``linux_`` + prefix, e.g. ``x86_64``. The ``linux_`` prefix is assumed as a + prerequisite for the current platform to be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/_structures.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/_structures.py index 800d5c558..90a6465f9 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/_structures.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/_structures.py @@ -1,85 +1,60 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function -class InfinityType(object): - def __repr__(self): - # type: () -> str +class InfinityType: + def __repr__(self) -> str: return "Infinity" - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): - # type: (object) -> bool + def __lt__(self, other: object) -> bool: return False - def __le__(self, other): - # type: (object) -> bool + def __le__(self, other: object) -> bool: return False - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): - # type: (object) -> bool - return not isinstance(other, self.__class__) - - def __gt__(self, other): - # type: (object) -> bool + def __gt__(self, other: object) -> bool: return True - def __ge__(self, other): - # type: (object) -> bool + def __ge__(self, other: object) -> bool: return True - def __neg__(self): - # type: (object) -> NegativeInfinityType + def __neg__(self: object) -> "NegativeInfinityType": return NegativeInfinity Infinity = InfinityType() -class NegativeInfinityType(object): - def __repr__(self): - # type: () -> str +class NegativeInfinityType: + def __repr__(self) -> str: return "-Infinity" - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(repr(self)) - def __lt__(self, other): - # type: (object) -> bool + def __lt__(self, other: object) -> bool: return True - def __le__(self, other): - # type: (object) -> bool + def __le__(self, other: object) -> bool: return True - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: return isinstance(other, self.__class__) - def __ne__(self, other): - # type: (object) -> bool - return not isinstance(other, self.__class__) - - def __gt__(self, other): - # type: (object) -> bool + def __gt__(self, other: object) -> bool: return False - def __ge__(self, other): - # type: (object) -> bool + def __ge__(self, other: object) -> bool: return False - def __neg__(self): - # type: (object) -> InfinityType + def __neg__(self: object) -> InfinityType: return Infinity diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/_typing.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/_typing.py deleted file mode 100644 index 77a8b9185..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/_typing.py +++ /dev/null @@ -1,48 +0,0 @@ -"""For neatly implementing static typing in packaging. - -`mypy` - the static type analysis tool we use - uses the `typing` module, which -provides core functionality fundamental to mypy's functioning. - -Generally, `typing` would be imported at runtime and used in that fashion - -it acts as a no-op at runtime and does not have any run-time overhead by -design. - -As it turns out, `typing` is not vendorable - it uses separate sources for -Python 2/Python 3. Thus, this codebase can not expect it to be present. -To work around this, mypy allows the typing import to be behind a False-y -optional to prevent it from running at runtime and type-comments can be used -to remove the need for the types to be accessible directly during runtime. - -This module provides the False-y guard in a nicely named fashion so that a -curious maintainer can reach here to read this. - -In packaging, all static-typing related imports should be guarded as follows: - - from packaging._typing import TYPE_CHECKING - - if TYPE_CHECKING: - from typing import ... - -Ref: https://github.com/python/mypy/issues/3216 -""" - -__all__ = ["TYPE_CHECKING", "cast"] - -# The TYPE_CHECKING constant defined by the typing module is False at runtime -# but True while type checking. -if False: # pragma: no cover - from typing import TYPE_CHECKING -else: - TYPE_CHECKING = False - -# typing's cast syntax requires calling typing.cast at runtime, but we don't -# want to import typing at runtime. Here, we inform the type checkers that -# we're importing `typing.cast` as `cast` and re-implement typing.cast's -# runtime behavior in a block that is ignored by type checkers. -if TYPE_CHECKING: # pragma: no cover - # not executed at runtime - from typing import cast -else: - # executed at runtime - def cast(type_, value): # noqa - return value diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/markers.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/markers.py index e0330ab6a..cb640e8f9 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/markers.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/markers.py @@ -1,12 +1,12 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import operator import os import platform import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union from pyparsing import ( # noqa: N817 Forward, @@ -20,16 +20,8 @@ stringStart, ) -from ._compat import string_types -from ._typing import TYPE_CHECKING from .specifiers import InvalidSpecifier, Specifier -if TYPE_CHECKING: # pragma: no cover - from typing import Any, Callable, Dict, List, Optional, Tuple, Union - - Operator = Callable[[str, str], bool] - - __all__ = [ "InvalidMarker", "UndefinedComparison", @@ -38,6 +30,8 @@ "default_environment", ] +Operator = Callable[[str, str], bool] + class InvalidMarker(ValueError): """ @@ -58,39 +52,32 @@ class UndefinedEnvironmentName(ValueError): """ -class Node(object): - def __init__(self, value): - # type: (Any) -> None +class Node: + def __init__(self, value: Any) -> None: self.value = value - def __str__(self): - # type: () -> str + def __str__(self) -> str: return str(self.value) - def __repr__(self): - # type: () -> str - return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" - def serialize(self): - # type: () -> str + def serialize(self) -> str: raise NotImplementedError class Variable(Node): - def serialize(self): - # type: () -> str + def serialize(self) -> str: return str(self) class Value(Node): - def serialize(self): - # type: () -> str - return '"{0}"'.format(self) + def serialize(self) -> str: + return f'"{self}"' class Op(Node): - def serialize(self): - # type: () -> str + def serialize(self) -> str: return str(self) @@ -151,18 +138,18 @@ def serialize(self): MARKER = stringStart + MARKER_EXPR + stringEnd -def _coerce_parse_result(results): - # type: (Union[ParseResults, List[Any]]) -> List[Any] +def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]: if isinstance(results, ParseResults): return [_coerce_parse_result(i) for i in results] else: return results -def _format_marker(marker, first=True): - # type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str +def _format_marker( + marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True +) -> str: - assert isinstance(marker, (list, tuple, string_types)) + assert isinstance(marker, (list, tuple, str)) # Sometimes we have a structure like [[...]] which is a single item list # where the single item is itself it's own list. In that case we want skip @@ -187,7 +174,7 @@ def _format_marker(marker, first=True): return marker -_operators = { +_operators: Dict[str, Operator] = { "in": lambda lhs, rhs: lhs in rhs, "not in": lambda lhs, rhs: lhs not in rhs, "<": operator.lt, @@ -196,11 +183,10 @@ def _format_marker(marker, first=True): "!=": operator.ne, ">=": operator.ge, ">": operator.gt, -} # type: Dict[str, Operator] +} -def _eval_op(lhs, op, rhs): - # type: (str, Op, str) -> bool +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: try: spec = Specifier("".join([op.serialize(), rhs])) except InvalidSpecifier: @@ -208,40 +194,36 @@ def _eval_op(lhs, op, rhs): else: return spec.contains(lhs) - oper = _operators.get(op.serialize()) # type: Optional[Operator] + oper: Optional[Operator] = _operators.get(op.serialize()) if oper is None: - raise UndefinedComparison( - "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) - ) + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") return oper(lhs, rhs) -class Undefined(object): +class Undefined: pass _undefined = Undefined() -def _get_env(environment, name): - # type: (Dict[str, str], str) -> str - value = environment.get(name, _undefined) # type: Union[str, Undefined] +def _get_env(environment: Dict[str, str], name: str) -> str: + value: Union[str, Undefined] = environment.get(name, _undefined) if isinstance(value, Undefined): raise UndefinedEnvironmentName( - "{0!r} does not exist in evaluation environment.".format(name) + f"{name!r} does not exist in evaluation environment." ) return value -def _evaluate_markers(markers, environment): - # type: (List[Any], Dict[str, str]) -> bool - groups = [[]] # type: List[List[bool]] +def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] for marker in markers: - assert isinstance(marker, (list, tuple, string_types)) + assert isinstance(marker, (list, tuple, str)) if isinstance(marker, list): groups[-1].append(_evaluate_markers(marker, environment)) @@ -264,8 +246,7 @@ def _evaluate_markers(markers, environment): return any(all(item) for item in groups) -def format_full_version(info): - # type: (sys._version_info) -> str +def format_full_version(info: "sys._version_info") -> str: version = "{0.major}.{0.minor}.{0.micro}".format(info) kind = info.releaselevel if kind != "final": @@ -273,18 +254,9 @@ def format_full_version(info): return version -def default_environment(): - # type: () -> Dict[str, str] - if hasattr(sys, "implementation"): - # Ignoring the `sys.implementation` reference for type checking due to - # mypy not liking that the attribute doesn't exist in Python 2.7 when - # run with the `--py27` flag. - iver = format_full_version(sys.implementation.version) # type: ignore - implementation_name = sys.implementation.name # type: ignore - else: - iver = "0" - implementation_name = "" - +def default_environment() -> Dict[str, str]: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name return { "implementation_name": implementation_name, "implementation_version": iver, @@ -300,27 +272,23 @@ def default_environment(): } -class Marker(object): - def __init__(self, marker): - # type: (str) -> None +class Marker: + def __init__(self, marker: str) -> None: try: self._markers = _coerce_parse_result(MARKER.parseString(marker)) except ParseException as e: - err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc : e.loc + 8] + raise InvalidMarker( + f"Invalid marker: {marker!r}, parse error at " + f"{marker[e.loc : e.loc + 8]!r}" ) - raise InvalidMarker(err_str) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return _format_marker(self._markers) - def __repr__(self): - # type: () -> str - return "".format(str(self)) + def __repr__(self) -> str: + return f"" - def evaluate(self, environment=None): - # type: (Optional[Dict[str, str]]) -> bool + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: """Evaluate a marker. Return the boolean from evaluating the given marker against the diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/requirements.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/requirements.py index aa69d50d1..53f9a3aa4 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/requirements.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/requirements.py @@ -1,13 +1,13 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import re import string -import sys +import urllib.parse +from typing import List, Optional as TOptional, Set -from pyparsing import ( # noqa: N817 +from pyparsing import ( # noqa Combine, Literal as L, Optional, @@ -20,19 +20,9 @@ stringStart, ) -from ._typing import TYPE_CHECKING from .markers import MARKER_EXPR, Marker from .specifiers import LegacySpecifier, Specifier, SpecifierSet -if sys.version_info[0] >= 3: - from urllib import parse as urlparse # pragma: no cover -else: # pragma: no cover - import urlparse - - -if TYPE_CHECKING: # pragma: no cover - from typing import List, Optional as TOptional, Set - class InvalidRequirement(ValueError): """ @@ -70,7 +60,7 @@ class InvalidRequirement(ValueError): VERSION_MANY = Combine( VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False )("_raw_spec") -_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY) _VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") @@ -94,7 +84,7 @@ class InvalidRequirement(ValueError): REQUIREMENT.parseString("x[]") -class Requirement(object): +class Requirement: """Parse a requirement. Parse a given requirement string into its parts, such as name, specifier, @@ -107,54 +97,50 @@ class Requirement(object): # the thing as well as the version? What about the markers? # TODO: Can we normalize the name and extra name? - def __init__(self, requirement_string): - # type: (str) -> None + def __init__(self, requirement_string: str) -> None: try: req = REQUIREMENT.parseString(requirement_string) except ParseException as e: raise InvalidRequirement( - 'Parse error at "{0!r}": {1}'.format( - requirement_string[e.loc : e.loc + 8], e.msg - ) + f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}' ) - self.name = req.name # type: str + self.name: str = req.name if req.url: - parsed_url = urlparse.urlparse(req.url) + parsed_url = urllib.parse.urlparse(req.url) if parsed_url.scheme == "file": - if urlparse.urlunparse(parsed_url) != req.url: + if urllib.parse.urlunparse(parsed_url) != req.url: raise InvalidRequirement("Invalid URL given") elif not (parsed_url.scheme and parsed_url.netloc) or ( not parsed_url.scheme and not parsed_url.netloc ): - raise InvalidRequirement("Invalid URL: {0}".format(req.url)) - self.url = req.url # type: TOptional[str] + raise InvalidRequirement(f"Invalid URL: {req.url}") + self.url: TOptional[str] = req.url else: self.url = None - self.extras = set(req.extras.asList() if req.extras else []) # type: Set[str] - self.specifier = SpecifierSet(req.specifier) # type: SpecifierSet - self.marker = req.marker if req.marker else None # type: TOptional[Marker] + self.extras: Set[str] = set(req.extras.asList() if req.extras else []) + self.specifier: SpecifierSet = SpecifierSet(req.specifier) + self.marker: TOptional[Marker] = req.marker if req.marker else None - def __str__(self): - # type: () -> str - parts = [self.name] # type: List[str] + def __str__(self) -> str: + parts: List[str] = [self.name] if self.extras: - parts.append("[{0}]".format(",".join(sorted(self.extras)))) + formatted_extras = ",".join(sorted(self.extras)) + parts.append(f"[{formatted_extras}]") if self.specifier: parts.append(str(self.specifier)) if self.url: - parts.append("@ {0}".format(self.url)) + parts.append(f"@ {self.url}") if self.marker: parts.append(" ") if self.marker: - parts.append("; {0}".format(self.marker)) + parts.append(f"; {self.marker}") return "".join(parts) - def __repr__(self): - # type: () -> str - return "".format(str(self)) + def __repr__(self) -> str: + return f"" diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/specifiers.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/specifiers.py index a6a83c1fe..0e218a6f9 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/specifiers.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/specifiers.py @@ -1,25 +1,33 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import abc import functools import itertools import re import warnings +from typing import ( + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Pattern, + Set, + Tuple, + TypeVar, + Union, +) -from ._compat import string_types, with_metaclass -from ._typing import TYPE_CHECKING from .utils import canonicalize_version from .version import LegacyVersion, Version, parse -if TYPE_CHECKING: # pragma: no cover - from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union - - ParsedVersion = Union[Version, LegacyVersion] - UnparsedVersion = Union[Version, LegacyVersion, str] - CallableOperator = Callable[[ParsedVersion, str], bool] +ParsedVersion = Union[Version, LegacyVersion] +UnparsedVersion = Union[Version, LegacyVersion, str] +VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion) +CallableOperator = Callable[[ParsedVersion, str], bool] class InvalidSpecifier(ValueError): @@ -28,64 +36,51 @@ class InvalidSpecifier(ValueError): """ -class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore +class BaseSpecifier(metaclass=abc.ABCMeta): @abc.abstractmethod - def __str__(self): - # type: () -> str + def __str__(self) -> str: """ Returns the str representation of this Specifier like object. This should be representative of the Specifier itself. """ @abc.abstractmethod - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: """ Returns a hash value for this Specifier like object. """ @abc.abstractmethod - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: """ Returns a boolean representing whether or not the two Specifier like objects are equal. """ - @abc.abstractmethod - def __ne__(self, other): - # type: (object) -> bool - """ - Returns a boolean representing whether or not the two Specifier like - objects are not equal. - """ - @abc.abstractproperty - def prereleases(self): - # type: () -> Optional[bool] + def prereleases(self) -> Optional[bool]: """ Returns whether or not pre-releases as a whole are allowed by this specifier. """ @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: """ Sets whether or not pre-releases as a whole are allowed by this specifier. """ @abc.abstractmethod - def contains(self, item, prereleases=None): - # type: (str, Optional[bool]) -> bool + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: """ Determines if the given item is contained within this specifier. """ @abc.abstractmethod - def filter(self, iterable, prereleases=None): - # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: """ Takes an iterable of items and filters them so that only items which are contained within this specifier are allowed in it. @@ -94,48 +89,43 @@ def filter(self, iterable, prereleases=None): class _IndividualSpecifier(BaseSpecifier): - _operators = {} # type: Dict[str, str] + _operators: Dict[str, str] = {} + _regex: Pattern[str] - def __init__(self, spec="", prereleases=None): - # type: (str, Optional[bool]) -> None + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: match = self._regex.search(spec) if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + raise InvalidSpecifier(f"Invalid specifier: '{spec}'") - self._spec = ( + self._spec: Tuple[str, str] = ( match.group("operator").strip(), match.group("version").strip(), - ) # type: Tuple[str, str] + ) # Store whether or not this Specifier should accept prereleases self._prereleases = prereleases - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: pre = ( - ", prereleases={0!r}".format(self.prereleases) + f", prereleases={self.prereleases!r}" if self._prereleases is not None else "" ) - return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) + return f"<{self.__class__.__name__}({str(self)!r}{pre})>" - def __str__(self): - # type: () -> str - return "{0}{1}".format(*self._spec) + def __str__(self) -> str: + return "{}{}".format(*self._spec) @property - def _canonical_spec(self): - # type: () -> Tuple[str, Union[Version, str]] + def _canonical_spec(self) -> Tuple[str, str]: return self._spec[0], canonicalize_version(self._spec[1]) - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(self._canonical_spec) - def __eq__(self, other): - # type: (object) -> bool - if isinstance(other, string_types): + def __eq__(self, other: object) -> bool: + if isinstance(other, str): try: other = self.__class__(str(other)) except InvalidSpecifier: @@ -145,57 +135,39 @@ def __eq__(self, other): return self._canonical_spec == other._canonical_spec - def __ne__(self, other): - # type: (object) -> bool - if isinstance(other, string_types): - try: - other = self.__class__(str(other)) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec != other._spec - - def _get_operator(self, op): - # type: (str) -> CallableOperator - operator_callable = getattr( - self, "_compare_{0}".format(self._operators[op]) - ) # type: CallableOperator + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) return operator_callable - def _coerce_version(self, version): - # type: (UnparsedVersion) -> ParsedVersion + def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: if not isinstance(version, (LegacyVersion, Version)): version = parse(version) return version @property - def operator(self): - # type: () -> str + def operator(self) -> str: return self._spec[0] @property - def version(self): - # type: () -> str + def version(self) -> str: return self._spec[1] @property - def prereleases(self): - # type: () -> Optional[bool] + def prereleases(self) -> Optional[bool]: return self._prereleases @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): - # type: (str) -> bool + def __contains__(self, item: str) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): - # type: (UnparsedVersion, Optional[bool]) -> bool + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: # Determine if prereleases are to be allowed or not. if prereleases is None: @@ -213,11 +185,12 @@ def contains(self, item, prereleases=None): # Actually do the comparison to determine if this item is contained # within this Specifier or not. - operator_callable = self._get_operator(self.operator) # type: CallableOperator + operator_callable: CallableOperator = self._get_operator(self.operator) return operator_callable(normalized_item, self.version) - def filter(self, iterable, prereleases=None): - # type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion] + def filter( + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: yielded = False found_prereleases = [] @@ -231,7 +204,7 @@ def filter(self, iterable, prereleases=None): if self.contains(parsed_version, **kw): # If our version is a prerelease, and we were not set to allow - # prereleases, then we'll store it for later incase nothing + # prereleases, then we'll store it for later in case nothing # else matches this specifier. if parsed_version.is_prerelease and not ( prereleases or self.prereleases @@ -276,9 +249,8 @@ class LegacySpecifier(_IndividualSpecifier): ">": "greater_than", } - def __init__(self, spec="", prereleases=None): - # type: (str, Optional[bool]) -> None - super(LegacySpecifier, self).__init__(spec, prereleases) + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + super().__init__(spec, prereleases) warnings.warn( "Creating a LegacyVersion has been deprecated and will be " @@ -286,44 +258,37 @@ def __init__(self, spec="", prereleases=None): DeprecationWarning, ) - def _coerce_version(self, version): - # type: (Union[ParsedVersion, str]) -> LegacyVersion + def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion: if not isinstance(version, LegacyVersion): version = LegacyVersion(str(version)) return version - def _compare_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective == self._coerce_version(spec) - def _compare_not_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective != self._coerce_version(spec) - def _compare_less_than_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: return prospective <= self._coerce_version(spec) - def _compare_greater_than_equal(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_greater_than_equal( + self, prospective: LegacyVersion, spec: str + ) -> bool: return prospective >= self._coerce_version(spec) - def _compare_less_than(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective < self._coerce_version(spec) - def _compare_greater_than(self, prospective, spec): - # type: (LegacyVersion, str) -> bool + def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: return prospective > self._coerce_version(spec) def _require_version_compare( - fn, # type: (Callable[[Specifier, ParsedVersion, str], bool]) -): - # type: (...) -> Callable[[Specifier, ParsedVersion, str], bool] + fn: Callable[["Specifier", ParsedVersion, str], bool] +) -> Callable[["Specifier", ParsedVersion, str], bool]: @functools.wraps(fn) - def wrapped(self, prospective, spec): - # type: (Specifier, ParsedVersion, str) -> bool + def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: if not isinstance(prospective, Version): return False return fn(self, prospective, spec) @@ -440,8 +405,7 @@ class Specifier(_IndividualSpecifier): } @_require_version_compare - def _compare_compatible(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: # Compatible releases have an equivalent combination of >= and ==. That # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to @@ -450,15 +414,9 @@ def _compare_compatible(self, prospective, spec): # the other specifiers. # We want everything but the last item in the version, but we want to - # ignore post and dev releases and we want to treat the pre-release as - # it's own separate segment. + # ignore suffix segments. prefix = ".".join( - list( - itertools.takewhile( - lambda x: (not x.startswith("post") and not x.startswith("dev")), - _version_split(spec), - ) - )[:-1] + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] ) # Add the prefix notation to the end of our string @@ -469,8 +427,7 @@ def _compare_compatible(self, prospective, spec): ) @_require_version_compare - def _compare_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: # We need special logic to handle prefix matching if spec.endswith(".*"): @@ -510,13 +467,11 @@ def _compare_equal(self, prospective, spec): return prospective == spec_version @_require_version_compare - def _compare_not_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: return not self._compare_equal(prospective, spec) @_require_version_compare - def _compare_less_than_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: # NB: Local version identifiers are NOT permitted in the version # specifier, so local version labels can be universally removed from @@ -524,8 +479,9 @@ def _compare_less_than_equal(self, prospective, spec): return Version(prospective.public) <= Version(spec) @_require_version_compare - def _compare_greater_than_equal(self, prospective, spec): - # type: (ParsedVersion, str) -> bool + def _compare_greater_than_equal( + self, prospective: ParsedVersion, spec: str + ) -> bool: # NB: Local version identifiers are NOT permitted in the version # specifier, so local version labels can be universally removed from @@ -533,8 +489,7 @@ def _compare_greater_than_equal(self, prospective, spec): return Version(prospective.public) >= Version(spec) @_require_version_compare - def _compare_less_than(self, prospective, spec_str): - # type: (ParsedVersion, str) -> bool + def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: # Convert our spec to a Version instance, since we'll want to work with # it as a version. @@ -560,8 +515,7 @@ def _compare_less_than(self, prospective, spec_str): return True @_require_version_compare - def _compare_greater_than(self, prospective, spec_str): - # type: (ParsedVersion, str) -> bool + def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: # Convert our spec to a Version instance, since we'll want to work with # it as a version. @@ -592,13 +546,11 @@ def _compare_greater_than(self, prospective, spec_str): # same version in the spec. return True - def _compare_arbitrary(self, prospective, spec): - # type: (Version, str) -> bool + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: return str(prospective).lower() == str(spec).lower() @property - def prereleases(self): - # type: () -> bool + def prereleases(self) -> bool: # If there is an explicit prereleases set for this, then we'll just # blindly use that. @@ -623,17 +575,15 @@ def prereleases(self): return False @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: self._prereleases = value _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") -def _version_split(version): - # type: (str) -> List[str] - result = [] # type: List[str] +def _version_split(version: str) -> List[str]: + result: List[str] = [] for item in version.split("."): match = _prefix_regex.search(item) if match: @@ -643,8 +593,13 @@ def _version_split(version): return result -def _pad_version(left, right): - # type: (List[str], List[str]) -> Tuple[List[str], List[str]] +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: left_split, right_split = [], [] # Get the release segment of our versions @@ -663,8 +618,9 @@ def _pad_version(left, right): class SpecifierSet(BaseSpecifier): - def __init__(self, specifiers="", prereleases=None): - # type: (str, Optional[bool]) -> None + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: # Split on , to break each individual specifier into it's own item, and # strip each item to remove leading/trailing whitespace. @@ -672,7 +628,7 @@ def __init__(self, specifiers="", prereleases=None): # Parsed each individual specifier, attempting first to make it a # Specifier and falling back to a LegacySpecifier. - parsed = set() + parsed: Set[_IndividualSpecifier] = set() for specifier in split_specifiers: try: parsed.add(Specifier(specifier)) @@ -686,27 +642,23 @@ def __init__(self, specifiers="", prereleases=None): # we accept prereleases or not. self._prereleases = prereleases - def __repr__(self): - # type: () -> str + def __repr__(self) -> str: pre = ( - ", prereleases={0!r}".format(self.prereleases) + f", prereleases={self.prereleases!r}" if self._prereleases is not None else "" ) - return "".format(str(self), pre) + return f"" - def __str__(self): - # type: () -> str + def __str__(self) -> str: return ",".join(sorted(str(s) for s in self._specs)) - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(self._specs) - def __and__(self, other): - # type: (Union[SpecifierSet, str]) -> SpecifierSet - if isinstance(other, string_types): + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": + if isinstance(other, str): other = SpecifierSet(other) elif not isinstance(other, SpecifierSet): return NotImplemented @@ -728,35 +680,22 @@ def __and__(self, other): return specifier - def __eq__(self, other): - # type: (object) -> bool - if isinstance(other, (string_types, _IndividualSpecifier)): + def __eq__(self, other: object) -> bool: + if isinstance(other, (str, _IndividualSpecifier)): other = SpecifierSet(str(other)) elif not isinstance(other, SpecifierSet): return NotImplemented return self._specs == other._specs - def __ne__(self, other): - # type: (object) -> bool - if isinstance(other, (string_types, _IndividualSpecifier)): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs != other._specs - - def __len__(self): - # type: () -> int + def __len__(self) -> int: return len(self._specs) - def __iter__(self): - # type: () -> Iterator[_IndividualSpecifier] + def __iter__(self) -> Iterator[_IndividualSpecifier]: return iter(self._specs) @property - def prereleases(self): - # type: () -> Optional[bool] + def prereleases(self) -> Optional[bool]: # If we have been given an explicit prerelease modifier, then we'll # pass that through here. @@ -774,16 +713,15 @@ def prereleases(self): return any(s.prereleases for s in self._specs) @prereleases.setter - def prereleases(self, value): - # type: (bool) -> None + def prereleases(self, value: bool) -> None: self._prereleases = value - def __contains__(self, item): - # type: (Union[ParsedVersion, str]) -> bool + def __contains__(self, item: UnparsedVersion) -> bool: return self.contains(item) - def contains(self, item, prereleases=None): - # type: (Union[ParsedVersion, str], Optional[bool]) -> bool + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: # Ensure that our item is a Version or LegacyVersion instance. if not isinstance(item, (LegacyVersion, Version)): @@ -811,11 +749,8 @@ def contains(self, item, prereleases=None): return all(s.contains(item, prereleases=prereleases) for s in self._specs) def filter( - self, - iterable, # type: Iterable[Union[ParsedVersion, str]] - prereleases=None, # type: Optional[bool] - ): - # type: (...) -> Iterable[Union[ParsedVersion, str]] + self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None + ) -> Iterable[VersionTypeVar]: # Determine if we're forcing a prerelease or not, if we're not forcing # one for this particular filter call, then we'll use whatever the @@ -834,8 +769,11 @@ def filter( # which will filter out any pre-releases, unless there are no final # releases, and which will filter out LegacyVersion in general. else: - filtered = [] # type: List[Union[ParsedVersion, str]] - found_prereleases = [] # type: List[Union[ParsedVersion, str]] + filtered: List[VersionTypeVar] = [] + found_prereleases: List[VersionTypeVar] = [] + + item: UnparsedVersion + parsed_version: Union[Version, LegacyVersion] for item in iterable: # Ensure that we some kind of Version class for this item. diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/tags.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/tags.py index d637f1b69..9a3d25a71 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/tags.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/tags.py @@ -2,81 +2,44 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import - -import distutils.util - -try: - from importlib.machinery import EXTENSION_SUFFIXES -except ImportError: # pragma: no cover - import imp - - EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] - del imp -import collections import logging -import os import platform -import re -import struct import sys import sysconfig -import warnings - -from ._typing import TYPE_CHECKING, cast - -if TYPE_CHECKING: # pragma: no cover - from typing import ( - IO, - Dict, - FrozenSet, - Iterable, - Iterator, - List, - Optional, - Sequence, - Tuple, - Union, - ) - - PythonVersion = Sequence[int] - MacVersion = Tuple[int, int] - GlibcVersion = Tuple[int, int] - +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from . import _manylinux, _musllinux logger = logging.getLogger(__name__) -INTERPRETER_SHORT_NAMES = { +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { "python": "py", # Generic. "cpython": "cp", "pypy": "pp", "ironpython": "ip", "jython": "jy", -} # type: Dict[str, str] +} _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 -_LEGACY_MANYLINUX_MAP = { - # CentOS 7 w/ glibc 2.17 (PEP 599) - (2, 17): "manylinux2014", - # CentOS 6 w/ glibc 2.12 (PEP 571) - (2, 12): "manylinux2010", - # CentOS 5 w/ glibc 2.5 (PEP 513) - (2, 5): "manylinux1", -} - -# If glibc ever changes its major version, we need to know what the last -# minor version was, so we can build the complete list of all versions. -# For now, guess what the highest minor version might be, assume it will -# be 50 for testing. Once this actually happens, update the dictionary -# with the actual value. -_LAST_GLIBC_MINOR = collections.defaultdict(lambda: 50) # type: Dict[int, int] -glibcVersion = collections.namedtuple("Version", ["major", "minor"]) - - -class Tag(object): +class Tag: """ A representation of the tag triple for a wheel. @@ -86,8 +49,7 @@ class Tag(object): __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] - def __init__(self, interpreter, abi, platform): - # type: (str, str, str) -> None + def __init__(self, interpreter: str, abi: str, platform: str) -> None: self._interpreter = interpreter.lower() self._abi = abi.lower() self._platform = platform.lower() @@ -99,46 +61,39 @@ def __init__(self, interpreter, abi, platform): self._hash = hash((self._interpreter, self._abi, self._platform)) @property - def interpreter(self): - # type: () -> str + def interpreter(self) -> str: return self._interpreter @property - def abi(self): - # type: () -> str + def abi(self) -> str: return self._abi @property - def platform(self): - # type: () -> str + def platform(self) -> str: return self._platform - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, Tag): return NotImplemented return ( - (self.platform == other.platform) - and (self.abi == other.abi) - and (self.interpreter == other.interpreter) + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) ) - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return self._hash - def __str__(self): - # type: () -> str - return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" - def __repr__(self): - # type: () -> str - return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + def __repr__(self) -> str: + return f"<{self} @ {id(self)}>" -def parse_tag(tag): - # type: (str) -> FrozenSet[Tag] +def parse_tag(tag: str) -> FrozenSet[Tag]: """ Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. @@ -154,24 +109,7 @@ def parse_tag(tag): return frozenset(tags) -def _warn_keyword_parameter(func_name, kwargs): - # type: (str, Dict[str, bool]) -> bool - """ - Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only. - """ - if not kwargs: - return False - elif len(kwargs) > 1 or "warn" not in kwargs: - kwargs.pop("warn", None) - arg = next(iter(kwargs.keys())) - raise TypeError( - "{}() got an unexpected keyword argument {!r}".format(func_name, arg) - ) - return kwargs["warn"] - - -def _get_config_var(name, warn=False): - # type: (str, bool) -> Union[int, str, None] +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: value = sysconfig.get_config_var(name) if value is None and warn: logger.debug( @@ -180,13 +118,11 @@ def _get_config_var(name, warn=False): return value -def _normalize_string(string): - # type: (str) -> str +def _normalize_string(string: str) -> str: return string.replace(".", "_").replace("-", "_") -def _abi3_applies(python_version): - # type: (PythonVersion) -> bool +def _abi3_applies(python_version: PythonVersion) -> bool: """ Determine if the Python version supports abi3. @@ -195,8 +131,7 @@ def _abi3_applies(python_version): return len(python_version) > 1 and tuple(python_version) >= (3, 2) -def _cpython_abis(py_version, warn=False): - # type: (PythonVersion, bool) -> List[str] +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: py_version = tuple(py_version) # To allow for version comparison. abis = [] version = _version_nodot(py_version[:2]) @@ -222,7 +157,7 @@ def _cpython_abis(py_version, warn=False): elif debug: # Debug builds can also load "normal" extension modules. # We can also assume no UCS-4 or pymalloc requirement. - abis.append("cp{version}".format(version=version)) + abis.append(f"cp{version}") abis.insert( 0, "cp{version}{debug}{pymalloc}{ucs4}".format( @@ -233,12 +168,12 @@ def _cpython_abis(py_version, warn=False): def cpython_tags( - python_version=None, # type: Optional[PythonVersion] - abis=None, # type: Optional[Iterable[str]] - platforms=None, # type: Optional[Iterable[str]] - **kwargs # type: bool -): - # type: (...) -> Iterator[Tag] + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: """ Yields the tags for a CPython interpreter. @@ -254,11 +189,10 @@ def cpython_tags( If 'abi3' or 'none' are specified in 'abis' then they will be yielded at their normal position and not at the beginning. """ - warn = _warn_keyword_parameter("cpython_tags", kwargs) if not python_version: python_version = sys.version_info[:2] - interpreter = "cp{}".format(_version_nodot(python_version[:2])) + interpreter = f"cp{_version_nodot(python_version[:2])}" if abis is None: if len(python_version) > 1: @@ -273,15 +207,13 @@ def cpython_tags( except ValueError: pass - platforms = list(platforms or _platform_tags()) + platforms = list(platforms or platform_tags()) for abi in abis: for platform_ in platforms: yield Tag(interpreter, abi, platform_) if _abi3_applies(python_version): - for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): - yield tag - for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): - yield tag + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) if _abi3_applies(python_version): for minor_version in range(python_version[1] - 1, 1, -1): @@ -292,20 +224,19 @@ def cpython_tags( yield Tag(interpreter, "abi3", platform_) -def _generic_abi(): - # type: () -> Iterator[str] +def _generic_abi() -> Iterator[str]: abi = sysconfig.get_config_var("SOABI") if abi: yield _normalize_string(abi) def generic_tags( - interpreter=None, # type: Optional[str] - abis=None, # type: Optional[Iterable[str]] - platforms=None, # type: Optional[Iterable[str]] - **kwargs # type: bool -): - # type: (...) -> Iterator[Tag] + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: """ Yields the tags for a generic interpreter. @@ -314,14 +245,13 @@ def generic_tags( The "none" ABI will be added if it was not explicitly provided. """ - warn = _warn_keyword_parameter("generic_tags", kwargs) if not interpreter: interp_name = interpreter_name() interp_version = interpreter_version(warn=warn) interpreter = "".join([interp_name, interp_version]) if abis is None: abis = _generic_abi() - platforms = list(platforms or _platform_tags()) + platforms = list(platforms or platform_tags()) abis = list(abis) if "none" not in abis: abis.append("none") @@ -330,8 +260,7 @@ def generic_tags( yield Tag(interpreter, abi, platform_) -def _py_interpreter_range(py_version): - # type: (PythonVersion) -> Iterator[str] +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: """ Yields Python versions in descending order. @@ -339,19 +268,18 @@ def _py_interpreter_range(py_version): all previous versions of that major version. """ if len(py_version) > 1: - yield "py{version}".format(version=_version_nodot(py_version[:2])) - yield "py{major}".format(major=py_version[0]) + yield f"py{_version_nodot(py_version[:2])}" + yield f"py{py_version[0]}" if len(py_version) > 1: for minor in range(py_version[1] - 1, -1, -1): - yield "py{version}".format(version=_version_nodot((py_version[0], minor))) + yield f"py{_version_nodot((py_version[0], minor))}" def compatible_tags( - python_version=None, # type: Optional[PythonVersion] - interpreter=None, # type: Optional[str] - platforms=None, # type: Optional[Iterable[str]] -): - # type: (...) -> Iterator[Tag] + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: """ Yields the sequence of tags that are compatible with a specific version of Python. @@ -362,7 +290,7 @@ def compatible_tags( """ if not python_version: python_version = sys.version_info[:2] - platforms = list(platforms or _platform_tags()) + platforms = list(platforms or platform_tags()) for version in _py_interpreter_range(python_version): for platform_ in platforms: yield Tag(version, "none", platform_) @@ -372,8 +300,7 @@ def compatible_tags( yield Tag(version, "none", "any") -def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): - # type: (str, bool) -> str +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: if not is_32bit: return arch @@ -383,8 +310,7 @@ def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): return "i386" -def _mac_binary_formats(version, cpu_arch): - # type: (MacVersion, str) -> List[str] +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: formats = [cpu_arch] if cpu_arch == "x86_64": if version < (10, 4): @@ -416,8 +342,9 @@ def _mac_binary_formats(version, cpu_arch): return formats -def mac_platforms(version=None, arch=None): - # type: (Optional[MacVersion], Optional[str]) -> Iterator[str] +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: """ Yields the platform tags for a macOS system. @@ -426,7 +353,7 @@ def mac_platforms(version=None, arch=None): generate platform tags for. Both parameters default to the appropriate value for the current system. """ - version_str, _, cpu_arch = platform.mac_ver() # type: ignore + version_str, _, cpu_arch = platform.mac_ver() if version is None: version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) else: @@ -487,320 +414,24 @@ def mac_platforms(version=None, arch=None): ) -# From PEP 513, PEP 600 -def _is_manylinux_compatible(name, arch, glibc_version): - # type: (str, str, GlibcVersion) -> bool - sys_glibc = _get_glibc_version() - if sys_glibc < glibc_version: - return False - # Check for presence of _manylinux module. - try: - import _manylinux # noqa - except ImportError: - pass - else: - if hasattr(_manylinux, "manylinux_compatible"): - result = _manylinux.manylinux_compatible( - glibc_version[0], glibc_version[1], arch - ) - if result is not None: - return bool(result) - else: - if glibc_version == (2, 5): - if hasattr(_manylinux, "manylinux1_compatible"): - return bool(_manylinux.manylinux1_compatible) - if glibc_version == (2, 12): - if hasattr(_manylinux, "manylinux2010_compatible"): - return bool(_manylinux.manylinux2010_compatible) - if glibc_version == (2, 17): - if hasattr(_manylinux, "manylinux2014_compatible"): - return bool(_manylinux.manylinux2014_compatible) - return True - - -def _glibc_version_string(): - # type: () -> Optional[str] - # Returns glibc version string, or None if not using glibc. - return _glibc_version_string_confstr() or _glibc_version_string_ctypes() - - -def _glibc_version_string_confstr(): - # type: () -> Optional[str] - """ - Primary implementation of glibc_version_string using os.confstr. - """ - # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely - # to be broken or missing. This strategy is used in the standard library - # platform module. - # https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183 - try: - # os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17". - version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821 - "CS_GNU_LIBC_VERSION" - ) - assert version_string is not None - _, version = version_string.split() # type: Tuple[str, str] - except (AssertionError, AttributeError, OSError, ValueError): - # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... - return None - return version - - -def _glibc_version_string_ctypes(): - # type: () -> Optional[str] - """ - Fallback implementation of glibc_version_string using ctypes. - """ - try: - import ctypes - except ImportError: - return None - - # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen - # manpage says, "If filename is NULL, then the returned handle is for the - # main program". This way we can let the linker do the work to figure out - # which libc our process is actually using. - # - # We must also handle the special case where the executable is not a - # dynamically linked executable. This can occur when using musl libc, - # for example. In this situation, dlopen() will error, leading to an - # OSError. Interestingly, at least in the case of musl, there is no - # errno set on the OSError. The single string argument used to construct - # OSError comes from libc itself and is therefore not portable to - # hard code here. In any case, failure to call dlopen() means we - # can proceed, so we bail on our attempt. - try: - # Note: typeshed is wrong here so we are ignoring this line. - process_namespace = ctypes.CDLL(None) # type: ignore - except OSError: - return None - - try: - gnu_get_libc_version = process_namespace.gnu_get_libc_version - except AttributeError: - # Symbol doesn't exist -> therefore, we are not linked to - # glibc. - return None - - # Call gnu_get_libc_version, which returns a string like "2.5" - gnu_get_libc_version.restype = ctypes.c_char_p - version_str = gnu_get_libc_version() # type: str - # py2 / py3 compatibility: - if not isinstance(version_str, str): - version_str = version_str.decode("ascii") - - return version_str - - -def _parse_glibc_version(version_str): - # type: (str) -> Tuple[int, int] - # Parse glibc version. - # - # We use a regexp instead of str.split because we want to discard any - # random junk that might come after the minor version -- this might happen - # in patched/forked versions of glibc (e.g. Linaro's version of glibc - # uses version strings like "2.20-2014.11"). See gh-3588. - m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) - if not m: - warnings.warn( - "Expected glibc version with 2 components major.minor," - " got: %s" % version_str, - RuntimeWarning, - ) - return -1, -1 - return (int(m.group("major")), int(m.group("minor"))) - - -_glibc_version = [] # type: List[Tuple[int, int]] - - -def _get_glibc_version(): - # type: () -> Tuple[int, int] - if _glibc_version: - return _glibc_version[0] - version_str = _glibc_version_string() - if version_str is None: - _glibc_version.append((-1, -1)) - else: - _glibc_version.append(_parse_glibc_version(version_str)) - return _glibc_version[0] - - -# Python does not provide platform information at sufficient granularity to -# identify the architecture of the running executable in some cases, so we -# determine it dynamically by reading the information from the running -# process. This only applies on Linux, which uses the ELF format. -class _ELFFileHeader(object): - # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header - class _InvalidELFFileHeader(ValueError): - """ - An invalid ELF file header was found. - """ - - ELF_MAGIC_NUMBER = 0x7F454C46 - ELFCLASS32 = 1 - ELFCLASS64 = 2 - ELFDATA2LSB = 1 - ELFDATA2MSB = 2 - EM_386 = 3 - EM_S390 = 22 - EM_ARM = 40 - EM_X86_64 = 62 - EF_ARM_ABIMASK = 0xFF000000 - EF_ARM_ABI_VER5 = 0x05000000 - EF_ARM_ABI_FLOAT_HARD = 0x00000400 - - def __init__(self, file): - # type: (IO[bytes]) -> None - def unpack(fmt): - # type: (str) -> int - try: - (result,) = struct.unpack( - fmt, file.read(struct.calcsize(fmt)) - ) # type: (int, ) - except struct.error: - raise _ELFFileHeader._InvalidELFFileHeader() - return result - - self.e_ident_magic = unpack(">I") - if self.e_ident_magic != self.ELF_MAGIC_NUMBER: - raise _ELFFileHeader._InvalidELFFileHeader() - self.e_ident_class = unpack("B") - if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}: - raise _ELFFileHeader._InvalidELFFileHeader() - self.e_ident_data = unpack("B") - if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}: - raise _ELFFileHeader._InvalidELFFileHeader() - self.e_ident_version = unpack("B") - self.e_ident_osabi = unpack("B") - self.e_ident_abiversion = unpack("B") - self.e_ident_pad = file.read(7) - format_h = "H" - format_i = "I" - format_q = "Q" - format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q - self.e_type = unpack(format_h) - self.e_machine = unpack(format_h) - self.e_version = unpack(format_i) - self.e_entry = unpack(format_p) - self.e_phoff = unpack(format_p) - self.e_shoff = unpack(format_p) - self.e_flags = unpack(format_i) - self.e_ehsize = unpack(format_h) - self.e_phentsize = unpack(format_h) - self.e_phnum = unpack(format_h) - self.e_shentsize = unpack(format_h) - self.e_shnum = unpack(format_h) - self.e_shstrndx = unpack(format_h) - - -def _get_elf_header(): - # type: () -> Optional[_ELFFileHeader] - try: - with open(sys.executable, "rb") as f: - elf_header = _ELFFileHeader(f) - except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader): - return None - return elf_header - - -def _is_linux_armhf(): - # type: () -> bool - # hard-float ABI can be detected from the ELF header of the running - # process - # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf - elf_header = _get_elf_header() - if elf_header is None: - return False - result = elf_header.e_ident_class == elf_header.ELFCLASS32 - result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB - result &= elf_header.e_machine == elf_header.EM_ARM - result &= ( - elf_header.e_flags & elf_header.EF_ARM_ABIMASK - ) == elf_header.EF_ARM_ABI_VER5 - result &= ( - elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD - ) == elf_header.EF_ARM_ABI_FLOAT_HARD - return result - - -def _is_linux_i686(): - # type: () -> bool - elf_header = _get_elf_header() - if elf_header is None: - return False - result = elf_header.e_ident_class == elf_header.ELFCLASS32 - result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB - result &= elf_header.e_machine == elf_header.EM_386 - return result - - -def _have_compatible_manylinux_abi(arch): - # type: (str) -> bool - if arch == "armv7l": - return _is_linux_armhf() - if arch == "i686": - return _is_linux_i686() - return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"} - - -def _manylinux_tags(linux, arch): - # type: (str, str) -> Iterator[str] - # Oldest glibc to be supported regardless of architecture is (2, 17). - too_old_glibc2 = glibcVersion(2, 16) - if arch in {"x86_64", "i686"}: - # On x86/i686 also oldest glibc to be supported is (2, 5). - too_old_glibc2 = glibcVersion(2, 4) - current_glibc = glibcVersion(*_get_glibc_version()) - glibc_max_list = [current_glibc] - # We can assume compatibility across glibc major versions. - # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 - # - # Build a list of maximum glibc versions so that we can - # output the canonical list of all glibc from current_glibc - # down to too_old_glibc2, including all intermediary versions. - for glibc_major in range(current_glibc.major - 1, 1, -1): - glibc_max_list.append(glibcVersion(glibc_major, _LAST_GLIBC_MINOR[glibc_major])) - for glibc_max in glibc_max_list: - if glibc_max.major == too_old_glibc2.major: - min_minor = too_old_glibc2.minor - else: - # For other glibc major versions oldest supported is (x, 0). - min_minor = -1 - for glibc_minor in range(glibc_max.minor, min_minor, -1): - glibc_version = (glibc_max.major, glibc_minor) - tag = "manylinux_{}_{}".format(*glibc_version) - if _is_manylinux_compatible(tag, arch, glibc_version): - yield linux.replace("linux", tag) - # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. - if glibc_version in _LEGACY_MANYLINUX_MAP: - legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] - if _is_manylinux_compatible(legacy_tag, arch, glibc_version): - yield linux.replace("linux", legacy_tag) - - -def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): - # type: (bool) -> Iterator[str] - linux = _normalize_string(distutils.util.get_platform()) +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) if is_32bit: if linux == "linux_x86_64": linux = "linux_i686" elif linux == "linux_aarch64": linux = "linux_armv7l" _, arch = linux.split("_", 1) - if _have_compatible_manylinux_abi(arch): - for tag in _manylinux_tags(linux, arch): - yield tag + yield from _manylinux.platform_tags(linux, arch) + yield from _musllinux.platform_tags(arch) yield linux -def _generic_platforms(): - # type: () -> Iterator[str] - yield _normalize_string(distutils.util.get_platform()) +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) -def _platform_tags(): - # type: () -> Iterator[str] +def platform_tags() -> Iterator[str]: """ Provides the platform tags for this installation. """ @@ -812,25 +443,18 @@ def _platform_tags(): return _generic_platforms() -def interpreter_name(): - # type: () -> str +def interpreter_name() -> str: """ Returns the name of the running interpreter. """ - try: - name = sys.implementation.name # type: ignore - except AttributeError: # pragma: no cover - # Python 2.7 compatibility. - name = platform.python_implementation().lower() + name = sys.implementation.name return INTERPRETER_SHORT_NAMES.get(name) or name -def interpreter_version(**kwargs): - # type: (bool) -> str +def interpreter_version(*, warn: bool = False) -> str: """ Returns the version of the running interpreter. """ - warn = _warn_keyword_parameter("interpreter_version", kwargs) version = _get_config_var("py_version_nodot", warn=warn) if version: version = str(version) @@ -839,28 +463,25 @@ def interpreter_version(**kwargs): return version -def _version_nodot(version): - # type: (PythonVersion) -> str +def _version_nodot(version: PythonVersion) -> str: return "".join(map(str, version)) -def sys_tags(**kwargs): - # type: (bool) -> Iterator[Tag] +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: """ Returns the sequence of tag triples for the running interpreter. The order of the sequence corresponds to priority order for the interpreter, from most to least important. """ - warn = _warn_keyword_parameter("sys_tags", kwargs) interp_name = interpreter_name() if interp_name == "cp": - for tag in cpython_tags(warn=warn): - yield tag + yield from cpython_tags(warn=warn) else: - for tag in generic_tags(): - yield tag + yield from generic_tags() - for tag in compatible_tags(): - yield tag + if interp_name == "pp": + yield from compatible_tags(interpreter="pp3") + else: + yield from compatible_tags() diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/utils.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/utils.py index 6e8c2a3e5..bab11b80c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/utils.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/utils.py @@ -1,22 +1,15 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import re +from typing import FrozenSet, NewType, Tuple, Union, cast -from ._typing import TYPE_CHECKING, cast from .tags import Tag, parse_tag from .version import InvalidVersion, Version -if TYPE_CHECKING: # pragma: no cover - from typing import FrozenSet, NewType, Tuple, Union - - BuildTag = Union[Tuple[()], Tuple[int, str]] - NormalizedName = NewType("NormalizedName", str) -else: - BuildTag = tuple - NormalizedName = str +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) class InvalidWheelFilename(ValueError): @@ -36,74 +29,75 @@ class InvalidSdistFilename(ValueError): _build_tag_regex = re.compile(r"(\d+)(.*)") -def canonicalize_name(name): - # type: (str) -> NormalizedName +def canonicalize_name(name: str) -> NormalizedName: # This is taken from PEP 503. value = _canonicalize_regex.sub("-", name).lower() return cast(NormalizedName, value) -def canonicalize_version(version): - # type: (Union[Version, str]) -> Union[Version, str] +def canonicalize_version(version: Union[Version, str]) -> str: """ This is very similar to Version.__str__, but has one subtle difference with the way it handles the release segment. """ - if not isinstance(version, Version): + if isinstance(version, str): try: - version = Version(version) + parsed = Version(version) except InvalidVersion: # Legacy versions cannot be normalized return version + else: + parsed = version parts = [] # Epoch - if version.epoch != 0: - parts.append("{0}!".format(version.epoch)) + if parsed.epoch != 0: + parts.append(f"{parsed.epoch}!") # Release segment # NB: This strips trailing '.0's to normalize - parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release))) # Pre-release - if version.pre is not None: - parts.append("".join(str(x) for x in version.pre)) + if parsed.pre is not None: + parts.append("".join(str(x) for x in parsed.pre)) # Post-release - if version.post is not None: - parts.append(".post{0}".format(version.post)) + if parsed.post is not None: + parts.append(f".post{parsed.post}") # Development release - if version.dev is not None: - parts.append(".dev{0}".format(version.dev)) + if parsed.dev is not None: + parts.append(f".dev{parsed.dev}") # Local version segment - if version.local is not None: - parts.append("+{0}".format(version.local)) + if parsed.local is not None: + parts.append(f"+{parsed.local}") return "".join(parts) -def parse_wheel_filename(filename): - # type: (str) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]] +def parse_wheel_filename( + filename: str, +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: if not filename.endswith(".whl"): raise InvalidWheelFilename( - "Invalid wheel filename (extension must be '.whl'): {0}".format(filename) + f"Invalid wheel filename (extension must be '.whl'): {filename}" ) filename = filename[:-4] dashes = filename.count("-") if dashes not in (4, 5): raise InvalidWheelFilename( - "Invalid wheel filename (wrong number of parts): {0}".format(filename) + f"Invalid wheel filename (wrong number of parts): {filename}" ) parts = filename.split("-", dashes - 2) name_part = parts[0] # See PEP 427 for the rules on escaping the project name if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: - raise InvalidWheelFilename("Invalid project name: {0}".format(filename)) + raise InvalidWheelFilename(f"Invalid project name: {filename}") name = canonicalize_name(name_part) version = Version(parts[1]) if dashes == 5: @@ -111,7 +105,7 @@ def parse_wheel_filename(filename): build_match = _build_tag_regex.match(build_part) if build_match is None: raise InvalidWheelFilename( - "Invalid build number: {0} in '{1}'".format(build_part, filename) + f"Invalid build number: {build_part} in '{filename}'" ) build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) else: @@ -120,18 +114,22 @@ def parse_wheel_filename(filename): return (name, version, build, tags) -def parse_sdist_filename(filename): - # type: (str) -> Tuple[NormalizedName, Version] - if not filename.endswith(".tar.gz"): +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: raise InvalidSdistFilename( - "Invalid sdist filename (extension must be '.tar.gz'): {0}".format(filename) + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename}" ) # We are requiring a PEP 440 version, which cannot contain dashes, # so we split on the last dash. - name_part, sep, version_part = filename[:-7].rpartition("-") + name_part, sep, version_part = file_stem.rpartition("-") if not sep: - raise InvalidSdistFilename("Invalid sdist filename: {0}".format(filename)) + raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") name = canonicalize_name(name_part) version = Version(version_part) diff --git a/conda_lock/_vendor/poetry/core/_vendor/packaging/version.py b/conda_lock/_vendor/poetry/core/_vendor/packaging/version.py index 517d91f24..de9a09a4e 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/packaging/version.py +++ b/conda_lock/_vendor/poetry/core/_vendor/packaging/version.py @@ -1,53 +1,45 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -from __future__ import absolute_import, division, print_function import collections import itertools import re import warnings +from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union -from ._structures import Infinity, NegativeInfinity -from ._typing import TYPE_CHECKING - -if TYPE_CHECKING: # pragma: no cover - from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union - - from ._structures import InfinityType, NegativeInfinityType - - InfiniteTypes = Union[InfinityType, NegativeInfinityType] - PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] - SubLocalType = Union[InfiniteTypes, int, str] - LocalType = Union[ - NegativeInfinityType, - Tuple[ - Union[ - SubLocalType, - Tuple[SubLocalType, str], - Tuple[NegativeInfinityType, SubLocalType], - ], - ..., - ], - ] - CmpKey = Tuple[ - int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType - ] - LegacyCmpKey = Tuple[int, Tuple[str, ...]] - VersionComparisonMethod = Callable[ - [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool - ] +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType __all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] +InfiniteTypes = Union[InfinityType, NegativeInfinityType] +PrePostDevType = Union[InfiniteTypes, Tuple[str, int]] +SubLocalType = Union[InfiniteTypes, int, str] +LocalType = Union[ + NegativeInfinityType, + Tuple[ + Union[ + SubLocalType, + Tuple[SubLocalType, str], + Tuple[NegativeInfinityType, SubLocalType], + ], + ..., + ], +] +CmpKey = Tuple[ + int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType +] +LegacyCmpKey = Tuple[int, Tuple[str, ...]] +VersionComparisonMethod = Callable[ + [Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool +] _Version = collections.namedtuple( "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) -def parse(version): - # type: (str) -> Union[LegacyVersion, Version] +def parse(version: str) -> Union["LegacyVersion", "Version"]: """ Parse the given version string and return either a :class:`Version` object or a :class:`LegacyVersion` object depending on if the given version is @@ -65,53 +57,46 @@ class InvalidVersion(ValueError): """ -class _BaseVersion(object): - _key = None # type: Union[CmpKey, LegacyCmpKey] +class _BaseVersion: + _key: Union[CmpKey, LegacyCmpKey] - def __hash__(self): - # type: () -> int + def __hash__(self) -> int: return hash(self._key) # Please keep the duplicated `isinstance` check # in the six comparisons hereunder # unless you find a way to avoid adding overhead function calls. - def __lt__(self, other): - # type: (_BaseVersion) -> bool + def __lt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key < other._key - def __le__(self, other): - # type: (_BaseVersion) -> bool + def __le__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key <= other._key - def __eq__(self, other): - # type: (object) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key == other._key - def __ge__(self, other): - # type: (_BaseVersion) -> bool + def __ge__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key >= other._key - def __gt__(self, other): - # type: (_BaseVersion) -> bool + def __gt__(self, other: "_BaseVersion") -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key > other._key - def __ne__(self, other): - # type: (object) -> bool + def __ne__(self, other: object) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented @@ -119,8 +104,7 @@ def __ne__(self, other): class LegacyVersion(_BaseVersion): - def __init__(self, version): - # type: (str) -> None + def __init__(self, version: str) -> None: self._version = str(version) self._key = _legacy_cmpkey(self._version) @@ -130,67 +114,54 @@ def __init__(self, version): DeprecationWarning, ) - def __str__(self): - # type: () -> str + def __str__(self) -> str: return self._version - def __repr__(self): - # type: () -> str - return "".format(repr(str(self))) + def __repr__(self) -> str: + return f"" @property - def public(self): - # type: () -> str + def public(self) -> str: return self._version @property - def base_version(self): - # type: () -> str + def base_version(self) -> str: return self._version @property - def epoch(self): - # type: () -> int + def epoch(self) -> int: return -1 @property - def release(self): - # type: () -> None + def release(self) -> None: return None @property - def pre(self): - # type: () -> None + def pre(self) -> None: return None @property - def post(self): - # type: () -> None + def post(self) -> None: return None @property - def dev(self): - # type: () -> None + def dev(self) -> None: return None @property - def local(self): - # type: () -> None + def local(self) -> None: return None @property - def is_prerelease(self): - # type: () -> bool + def is_prerelease(self) -> bool: return False @property - def is_postrelease(self): - # type: () -> bool + def is_postrelease(self) -> bool: return False @property - def is_devrelease(self): - # type: () -> bool + def is_devrelease(self) -> bool: return False @@ -205,8 +176,7 @@ def is_devrelease(self): } -def _parse_version_parts(s): - # type: (str) -> Iterator[str] +def _parse_version_parts(s: str) -> Iterator[str]: for part in _legacy_version_component_re.split(s): part = _legacy_version_replacement_map.get(part, part) @@ -223,8 +193,7 @@ def _parse_version_parts(s): yield "*final" -def _legacy_cmpkey(version): - # type: (str) -> LegacyCmpKey +def _legacy_cmpkey(version: str) -> LegacyCmpKey: # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch # greater than or equal to 0. This will effectively put the LegacyVersion, @@ -234,7 +203,7 @@ def _legacy_cmpkey(version): # This scheme is taken from pkg_resources.parse_version setuptools prior to # it's adoption of the packaging library. - parts = [] # type: List[str] + parts: List[str] = [] for part in _parse_version_parts(version.lower()): if part.startswith("*"): # remove "-" before a prerelease tag @@ -289,13 +258,12 @@ class Version(_BaseVersion): _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) - def __init__(self, version): - # type: (str) -> None + def __init__(self, version: str) -> None: # Validate the version and parse it into pieces match = self._regex.search(version) if not match: - raise InvalidVersion("Invalid version: '{0}'".format(version)) + raise InvalidVersion(f"Invalid version: '{version}'") # Store the parsed out pieces of the version self._version = _Version( @@ -319,17 +287,15 @@ def __init__(self, version): self._version.local, ) - def __repr__(self): - # type: () -> str - return "".format(repr(str(self))) + def __repr__(self) -> str: + return f"" - def __str__(self): - # type: () -> str + def __str__(self) -> str: parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -340,67 +306,59 @@ def __str__(self): # Post-release if self.post is not None: - parts.append(".post{0}".format(self.post)) + parts.append(f".post{self.post}") # Development release if self.dev is not None: - parts.append(".dev{0}".format(self.dev)) + parts.append(f".dev{self.dev}") # Local version segment if self.local is not None: - parts.append("+{0}".format(self.local)) + parts.append(f"+{self.local}") return "".join(parts) @property - def epoch(self): - # type: () -> int - _epoch = self._version.epoch # type: int + def epoch(self) -> int: + _epoch: int = self._version.epoch return _epoch @property - def release(self): - # type: () -> Tuple[int, ...] - _release = self._version.release # type: Tuple[int, ...] + def release(self) -> Tuple[int, ...]: + _release: Tuple[int, ...] = self._version.release return _release @property - def pre(self): - # type: () -> Optional[Tuple[str, int]] - _pre = self._version.pre # type: Optional[Tuple[str, int]] + def pre(self) -> Optional[Tuple[str, int]]: + _pre: Optional[Tuple[str, int]] = self._version.pre return _pre @property - def post(self): - # type: () -> Optional[Tuple[str, int]] + def post(self) -> Optional[int]: return self._version.post[1] if self._version.post else None @property - def dev(self): - # type: () -> Optional[Tuple[str, int]] + def dev(self) -> Optional[int]: return self._version.dev[1] if self._version.dev else None @property - def local(self): - # type: () -> Optional[str] + def local(self) -> Optional[str]: if self._version.local: return ".".join(str(x) for x in self._version.local) else: return None @property - def public(self): - # type: () -> str + def public(self) -> str: return str(self).split("+", 1)[0] @property - def base_version(self): - # type: () -> str + def base_version(self) -> str: parts = [] # Epoch if self.epoch != 0: - parts.append("{0}!".format(self.epoch)) + parts.append(f"{self.epoch}!") # Release segment parts.append(".".join(str(x) for x in self.release)) @@ -408,41 +366,33 @@ def base_version(self): return "".join(parts) @property - def is_prerelease(self): - # type: () -> bool + def is_prerelease(self) -> bool: return self.dev is not None or self.pre is not None @property - def is_postrelease(self): - # type: () -> bool + def is_postrelease(self) -> bool: return self.post is not None @property - def is_devrelease(self): - # type: () -> bool + def is_devrelease(self) -> bool: return self.dev is not None @property - def major(self): - # type: () -> int + def major(self) -> int: return self.release[0] if len(self.release) >= 1 else 0 @property - def minor(self): - # type: () -> int + def minor(self) -> int: return self.release[1] if len(self.release) >= 2 else 0 @property - def micro(self): - # type: () -> int + def micro(self) -> int: return self.release[2] if len(self.release) >= 3 else 0 def _parse_letter_version( - letter, # type: str - number, # type: Union[str, bytes, SupportsInt] -): - # type: (...) -> Optional[Tuple[str, int]] + letter: str, number: Union[str, bytes, SupportsInt] +) -> Optional[Tuple[str, int]]: if letter: # We consider there to be an implicit 0 in a pre-release if there is @@ -479,8 +429,7 @@ def _parse_letter_version( _local_version_separators = re.compile(r"[\._-]") -def _parse_local_version(local): - # type: (str) -> Optional[LocalType] +def _parse_local_version(local: str) -> Optional[LocalType]: """ Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). """ @@ -493,14 +442,13 @@ def _parse_local_version(local): def _cmpkey( - epoch, # type: int - release, # type: Tuple[int, ...] - pre, # type: Optional[Tuple[str, int]] - post, # type: Optional[Tuple[str, int]] - dev, # type: Optional[Tuple[str, int]] - local, # type: Optional[Tuple[SubLocalType]] -): - # type: (...) -> CmpKey + epoch: int, + release: Tuple[int, ...], + pre: Optional[Tuple[str, int]], + post: Optional[Tuple[str, int]], + dev: Optional[Tuple[str, int]], + local: Optional[Tuple[SubLocalType]], +) -> CmpKey: # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now @@ -516,7 +464,7 @@ def _cmpkey( # if there is not a pre or a post segment. If we have one of those then # the normal sorting rules will handle this case correctly. if pre is None and post is None and dev is not None: - _pre = NegativeInfinity # type: PrePostDevType + _pre: PrePostDevType = NegativeInfinity # Versions without a pre-release (except as noted above) should sort after # those with one. elif pre is None: @@ -526,21 +474,21 @@ def _cmpkey( # Versions without a post segment should sort before those with one. if post is None: - _post = NegativeInfinity # type: PrePostDevType + _post: PrePostDevType = NegativeInfinity else: _post = post # Versions without a development segment should sort after those with one. if dev is None: - _dev = Infinity # type: PrePostDevType + _dev: PrePostDevType = Infinity else: _dev = dev if local is None: # Versions without a local segment should sort before those with one. - _local = NegativeInfinity # type: LocalType + _local: LocalType = NegativeInfinity else: # Versions with a local segment need that segment parsed to implement # the sorting rules in PEP440. diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing.py deleted file mode 100644 index 581d5bbb8..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/pyparsing.py +++ /dev/null @@ -1,7107 +0,0 @@ -# -*- coding: utf-8 -*- -# module pyparsing.py -# -# Copyright (c) 2003-2019 Paul T. McGuire -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -__doc__ = \ -""" -pyparsing module - Classes and methods to define and execute parsing grammars -============================================================================= - -The pyparsing module is an alternative approach to creating and -executing simple grammars, vs. the traditional lex/yacc approach, or the -use of regular expressions. With pyparsing, you don't need to learn -a new syntax for defining grammars or matching expressions - the parsing -module provides a library of classes that you use to construct the -grammar directly in Python. - -Here is a program to parse "Hello, World!" (or any greeting of the form -``", !"``), built up using :class:`Word`, -:class:`Literal`, and :class:`And` elements -(the :class:`'+'` operators create :class:`And` expressions, -and the strings are auto-converted to :class:`Literal` expressions):: - - from pyparsing import Word, alphas - - # define grammar of a greeting - greet = Word(alphas) + "," + Word(alphas) + "!" - - hello = "Hello, World!" - print (hello, "->", greet.parseString(hello)) - -The program outputs the following:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the -self-explanatory class names, and the use of '+', '|' and '^' operators. - -The :class:`ParseResults` object returned from -:class:`ParserElement.parseString` can be -accessed as a nested list, a dictionary, or an object with named -attributes. - -The pyparsing module handles some of the problems that are typically -vexing when writing text parsers: - - - extra or missing whitespace (the above program will also handle - "Hello,World!", "Hello , World !", etc.) - - quoted strings - - embedded comments - - -Getting Started - ------------------ -Visit the classes :class:`ParserElement` and :class:`ParseResults` to -see the base classes that most other pyparsing -classes inherit from. Use the docstrings for examples of how to: - - - construct literal match expressions from :class:`Literal` and - :class:`CaselessLiteral` classes - - construct character word-group expressions using the :class:`Word` - class - - see how to create repetitive expressions using :class:`ZeroOrMore` - and :class:`OneOrMore` classes - - use :class:`'+'`, :class:`'|'`, :class:`'^'`, - and :class:`'&'` operators to combine simple expressions into - more complex ones - - associate names with your parsed results using - :class:`ParserElement.setResultsName` - - access the parsed data, which is returned as a :class:`ParseResults` - object - - find some helpful expression short-cuts like :class:`delimitedList` - and :class:`oneOf` - - find more useful common expressions in the :class:`pyparsing_common` - namespace class -""" - -__version__ = "2.4.7" -__versionTime__ = "30 Mar 2020 00:43 UTC" -__author__ = "Paul McGuire " - -import string -from weakref import ref as wkref -import copy -import sys -import warnings -import re -import sre_constants -import collections -import pprint -import traceback -import types -from datetime import datetime -from operator import itemgetter -import itertools -from functools import wraps -from contextlib import contextmanager - -try: - # Python 3 - from itertools import filterfalse -except ImportError: - from itertools import ifilterfalse as filterfalse - -try: - from _thread import RLock -except ImportError: - from threading import RLock - -try: - # Python 3 - from collections.abc import Iterable - from collections.abc import MutableMapping, Mapping -except ImportError: - # Python 2.7 - from collections import Iterable - from collections import MutableMapping, Mapping - -try: - from collections import OrderedDict as _OrderedDict -except ImportError: - try: - from ordereddict import OrderedDict as _OrderedDict - except ImportError: - _OrderedDict = None - -try: - from types import SimpleNamespace -except ImportError: - class SimpleNamespace: pass - -# version compatibility configuration -__compat__ = SimpleNamespace() -__compat__.__doc__ = """ - A cross-version compatibility configuration for pyparsing features that will be - released in a future version. By setting values in this configuration to True, - those features can be enabled in prior versions for compatibility development - and testing. - - - collect_all_And_tokens - flag to enable fix for Issue #63 that fixes erroneous grouping - of results names when an And expression is nested within an Or or MatchFirst; set to - True to enable bugfix released in pyparsing 2.3.0, or False to preserve - pre-2.3.0 handling of named results -""" -__compat__.collect_all_And_tokens = True - -__diag__ = SimpleNamespace() -__diag__.__doc__ = """ -Diagnostic configuration (all default to False) - - warn_multiple_tokens_in_named_alternation - flag to enable warnings when a results - name is defined on a MatchFirst or Or expression with one or more And subexpressions - (only warns if __compat__.collect_all_And_tokens is False) - - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results - name is defined on a containing expression with ungrouped subexpressions that also - have results names - - warn_name_set_on_empty_Forward - flag to enable warnings whan a Forward is defined - with a results name, but has no contents defined - - warn_on_multiple_string_args_to_oneof - flag to enable warnings whan oneOf is - incorrectly called with multiple str arguments - - enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent - calls to ParserElement.setName() -""" -__diag__.warn_multiple_tokens_in_named_alternation = False -__diag__.warn_ungrouped_named_tokens_in_collection = False -__diag__.warn_name_set_on_empty_Forward = False -__diag__.warn_on_multiple_string_args_to_oneof = False -__diag__.enable_debug_on_named_expressions = False -__diag__._all_names = [nm for nm in vars(__diag__) if nm.startswith("enable_") or nm.startswith("warn_")] - -def _enable_all_warnings(): - __diag__.warn_multiple_tokens_in_named_alternation = True - __diag__.warn_ungrouped_named_tokens_in_collection = True - __diag__.warn_name_set_on_empty_Forward = True - __diag__.warn_on_multiple_string_args_to_oneof = True -__diag__.enable_all_warnings = _enable_all_warnings - - -__all__ = ['__version__', '__versionTime__', '__author__', '__compat__', '__diag__', - 'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', - 'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', - 'PrecededBy', 'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', - 'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', - 'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', - 'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', - 'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', 'Char', - 'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', - 'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', - 'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', - 'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', - 'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', - 'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', - 'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', - 'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', - 'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', - 'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation', 'locatedExpr', 'withClass', - 'CloseMatch', 'tokenMap', 'pyparsing_common', 'pyparsing_unicode', 'unicode_set', - 'conditionAsParseAction', 're', - ] - -system_version = tuple(sys.version_info)[:3] -PY_3 = system_version[0] == 3 -if PY_3: - _MAX_INT = sys.maxsize - basestring = str - unichr = chr - unicode = str - _ustr = str - - # build list of single arg builtins, that can be used as parse actions - singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max] - -else: - _MAX_INT = sys.maxint - range = xrange - - def _ustr(obj): - """Drop-in replacement for str(obj) that tries to be Unicode - friendly. It first tries str(obj). If that fails with - a UnicodeEncodeError, then it tries unicode(obj). It then - < returns the unicode object | encodes it with the default - encoding | ... >. - """ - if isinstance(obj, unicode): - return obj - - try: - # If this works, then _ustr(obj) has the same behaviour as str(obj), so - # it won't break any existing code. - return str(obj) - - except UnicodeEncodeError: - # Else encode it - ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace') - xmlcharref = Regex(r'&#\d+;') - xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:]) - return xmlcharref.transformString(ret) - - # build list of single arg builtins, tolerant of Python version, that can be used as parse actions - singleArgBuiltins = [] - import __builtin__ - - for fname in "sum len sorted reversed list tuple set any all min max".split(): - try: - singleArgBuiltins.append(getattr(__builtin__, fname)) - except AttributeError: - continue - -_generatorType = type((y for y in range(1))) - -def _xml_escape(data): - """Escape &, <, >, ", ', etc. in a string of data.""" - - # ampersand must be replaced first - from_symbols = '&><"\'' - to_symbols = ('&' + s + ';' for s in "amp gt lt quot apos".split()) - for from_, to_ in zip(from_symbols, to_symbols): - data = data.replace(from_, to_) - return data - -alphas = string.ascii_uppercase + string.ascii_lowercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) -printables = "".join(c for c in string.printable if c not in string.whitespace) - - -def conditionAsParseAction(fn, message=None, fatal=False): - msg = message if message is not None else "failed user-defined condition" - exc_type = ParseFatalException if fatal else ParseException - fn = _trim_arity(fn) - - @wraps(fn) - def pa(s, l, t): - if not bool(fn(s, l, t)): - raise exc_type(s, l, msg) - - return pa - -class ParseBaseException(Exception): - """base exception class for all parsing runtime exceptions""" - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__(self, pstr, loc=0, msg=None, elem=None): - self.loc = loc - if msg is None: - self.msg = pstr - self.pstr = "" - else: - self.msg = msg - self.pstr = pstr - self.parserElement = elem - self.args = (pstr, loc, msg) - - @classmethod - def _from_exception(cls, pe): - """ - internal factory method to simplify creating one type of ParseException - from another - avoids having __init__ signature conflicts among subclasses - """ - return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) - - def __getattr__(self, aname): - """supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - if aname == "lineno": - return lineno(self.loc, self.pstr) - elif aname in ("col", "column"): - return col(self.loc, self.pstr) - elif aname == "line": - return line(self.loc, self.pstr) - else: - raise AttributeError(aname) - - def __str__(self): - if self.pstr: - if self.loc >= len(self.pstr): - foundstr = ', found end of text' - else: - foundstr = (', found %r' % self.pstr[self.loc:self.loc + 1]).replace(r'\\', '\\') - else: - foundstr = '' - return ("%s%s (at char %d), (line:%d, col:%d)" % - (self.msg, foundstr, self.loc, self.lineno, self.column)) - def __repr__(self): - return _ustr(self) - def markInputline(self, markerString=">!<"): - """Extracts the exception line from the input string, and marks - the location of the exception with a special symbol. - """ - line_str = self.line - line_column = self.column - 1 - if markerString: - line_str = "".join((line_str[:line_column], - markerString, line_str[line_column:])) - return line_str.strip() - def __dir__(self): - return "lineno col line".split() + dir(type(self)) - -class ParseException(ParseBaseException): - """ - Exception thrown when parse expressions don't match class; - supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - - Example:: - - try: - Word(nums).setName("integer").parseString("ABC") - except ParseException as pe: - print(pe) - print("column: {}".format(pe.col)) - - prints:: - - Expected integer (at char 0), (line:1, col:1) - column: 1 - - """ - - @staticmethod - def explain(exc, depth=16): - """ - Method to take an exception and translate the Python internal traceback into a list - of the pyparsing expressions that caused the exception to be raised. - - Parameters: - - - exc - exception raised during parsing (need not be a ParseException, in support - of Python exceptions that might be raised in a parse action) - - depth (default=16) - number of levels back in the stack trace to list expression - and function names; if None, the full stack trace names will be listed; if 0, only - the failing input line, marker, and exception string will be shown - - Returns a multi-line string listing the ParserElements and/or function names in the - exception's stack trace. - - Note: the diagnostic output will include string representations of the expressions - that failed to parse. These representations will be more helpful if you use `setName` to - give identifiable names to your expressions. Otherwise they will use the default string - forms, which may be cryptic to read. - - explain() is only supported under Python 3. - """ - import inspect - - if depth is None: - depth = sys.getrecursionlimit() - ret = [] - if isinstance(exc, ParseBaseException): - ret.append(exc.line) - ret.append(' ' * (exc.col - 1) + '^') - ret.append("{0}: {1}".format(type(exc).__name__, exc)) - - if depth > 0: - callers = inspect.getinnerframes(exc.__traceback__, context=depth) - seen = set() - for i, ff in enumerate(callers[-depth:]): - frm = ff[0] - - f_self = frm.f_locals.get('self', None) - if isinstance(f_self, ParserElement): - if frm.f_code.co_name not in ('parseImpl', '_parseNoCache'): - continue - if f_self in seen: - continue - seen.add(f_self) - - self_type = type(f_self) - ret.append("{0}.{1} - {2}".format(self_type.__module__, - self_type.__name__, - f_self)) - elif f_self is not None: - self_type = type(f_self) - ret.append("{0}.{1}".format(self_type.__module__, - self_type.__name__)) - else: - code = frm.f_code - if code.co_name in ('wrapper', ''): - continue - - ret.append("{0}".format(code.co_name)) - - depth -= 1 - if not depth: - break - - return '\n'.join(ret) - - -class ParseFatalException(ParseBaseException): - """user-throwable exception thrown when inconsistent parse content - is found; stops all parsing immediately""" - pass - -class ParseSyntaxException(ParseFatalException): - """just like :class:`ParseFatalException`, but thrown internally - when an :class:`ErrorStop` ('-' operator) indicates - that parsing is to stop immediately because an unbacktrackable - syntax error has been found. - """ - pass - -#~ class ReparseException(ParseBaseException): - #~ """Experimental class - parse actions can raise this exception to cause - #~ pyparsing to reparse the input string: - #~ - with a modified input string, and/or - #~ - with a modified start location - #~ Set the values of the ReparseException in the constructor, and raise the - #~ exception in a parse action to cause pyparsing to use the new string/location. - #~ Setting the values as None causes no change to be made. - #~ """ - #~ def __init_( self, newstring, restartLoc ): - #~ self.newParseText = newstring - #~ self.reparseLoc = restartLoc - -class RecursiveGrammarException(Exception): - """exception thrown by :class:`ParserElement.validate` if the - grammar could be improperly recursive - """ - def __init__(self, parseElementList): - self.parseElementTrace = parseElementList - - def __str__(self): - return "RecursiveGrammarException: %s" % self.parseElementTrace - -class _ParseResultsWithOffset(object): - def __init__(self, p1, p2): - self.tup = (p1, p2) - def __getitem__(self, i): - return self.tup[i] - def __repr__(self): - return repr(self.tup[0]) - def setOffset(self, i): - self.tup = (self.tup[0], i) - -class ParseResults(object): - """Structured parse results, to provide multiple means of access to - the parsed data: - - - as a list (``len(results)``) - - by list index (``results[0], results[1]``, etc.) - - by attribute (``results.`` - see :class:`ParserElement.setResultsName`) - - Example:: - - integer = Word(nums) - date_str = (integer.setResultsName("year") + '/' - + integer.setResultsName("month") + '/' - + integer.setResultsName("day")) - # equivalent form: - # date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - # parseString returns a ParseResults object - result = date_str.parseString("1999/12/31") - - def test(s, fn=repr): - print("%s -> %s" % (s, fn(eval(s)))) - test("list(result)") - test("result[0]") - test("result['month']") - test("result.day") - test("'month' in result") - test("'minutes' in result") - test("result.dump()", str) - - prints:: - - list(result) -> ['1999', '/', '12', '/', '31'] - result[0] -> '1999' - result['month'] -> '12' - result.day -> '31' - 'month' in result -> True - 'minutes' in result -> False - result.dump() -> ['1999', '/', '12', '/', '31'] - - day: 31 - - month: 12 - - year: 1999 - """ - def __new__(cls, toklist=None, name=None, asList=True, modal=True): - if isinstance(toklist, cls): - return toklist - retobj = object.__new__(cls) - retobj.__doinit = True - return retobj - - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__(self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance): - if self.__doinit: - self.__doinit = False - self.__name = None - self.__parent = None - self.__accumNames = {} - self.__asList = asList - self.__modal = modal - if toklist is None: - toklist = [] - if isinstance(toklist, list): - self.__toklist = toklist[:] - elif isinstance(toklist, _generatorType): - self.__toklist = list(toklist) - else: - self.__toklist = [toklist] - self.__tokdict = dict() - - if name is not None and name: - if not modal: - self.__accumNames[name] = 0 - if isinstance(name, int): - name = _ustr(name) # will always return a str, but use _ustr for consistency - self.__name = name - if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None, '', [])): - if isinstance(toklist, basestring): - toklist = [toklist] - if asList: - if isinstance(toklist, ParseResults): - self[name] = _ParseResultsWithOffset(ParseResults(toklist.__toklist), 0) - else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]), 0) - self[name].__name = name - else: - try: - self[name] = toklist[0] - except (KeyError, TypeError, IndexError): - self[name] = toklist - - def __getitem__(self, i): - if isinstance(i, (int, slice)): - return self.__toklist[i] - else: - if i not in self.__accumNames: - return self.__tokdict[i][-1][0] - else: - return ParseResults([v[0] for v in self.__tokdict[i]]) - - def __setitem__(self, k, v, isinstance=isinstance): - if isinstance(v, _ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k, list()) + [v] - sub = v[0] - elif isinstance(k, (int, slice)): - self.__toklist[k] = v - sub = v - else: - self.__tokdict[k] = self.__tokdict.get(k, list()) + [_ParseResultsWithOffset(v, 0)] - sub = v - if isinstance(sub, ParseResults): - sub.__parent = wkref(self) - - def __delitem__(self, i): - if isinstance(i, (int, slice)): - mylen = len(self.__toklist) - del self.__toklist[i] - - # convert int to slice - if isinstance(i, int): - if i < 0: - i += mylen - i = slice(i, i + 1) - # get removed indices - removed = list(range(*i.indices(mylen))) - removed.reverse() - # fixup indices in token dictionary - for name, occurrences in self.__tokdict.items(): - for j in removed: - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) - else: - del self.__tokdict[i] - - def __contains__(self, k): - return k in self.__tokdict - - def __len__(self): - return len(self.__toklist) - - def __bool__(self): - return (not not self.__toklist) - __nonzero__ = __bool__ - - def __iter__(self): - return iter(self.__toklist) - - def __reversed__(self): - return iter(self.__toklist[::-1]) - - def _iterkeys(self): - if hasattr(self.__tokdict, "iterkeys"): - return self.__tokdict.iterkeys() - else: - return iter(self.__tokdict) - - def _itervalues(self): - return (self[k] for k in self._iterkeys()) - - def _iteritems(self): - return ((k, self[k]) for k in self._iterkeys()) - - if PY_3: - keys = _iterkeys - """Returns an iterator of all named result keys.""" - - values = _itervalues - """Returns an iterator of all named result values.""" - - items = _iteritems - """Returns an iterator of all named result key-value tuples.""" - - else: - iterkeys = _iterkeys - """Returns an iterator of all named result keys (Python 2.x only).""" - - itervalues = _itervalues - """Returns an iterator of all named result values (Python 2.x only).""" - - iteritems = _iteritems - """Returns an iterator of all named result key-value tuples (Python 2.x only).""" - - def keys(self): - """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x).""" - return list(self.iterkeys()) - - def values(self): - """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x).""" - return list(self.itervalues()) - - def items(self): - """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x).""" - return list(self.iteritems()) - - def haskeys(self): - """Since keys() returns an iterator, this method is helpful in bypassing - code that looks for the existence of any defined results names.""" - return bool(self.__tokdict) - - def pop(self, *args, **kwargs): - """ - Removes and returns item at specified index (default= ``last``). - Supports both ``list`` and ``dict`` semantics for ``pop()``. If - passed no argument or an integer argument, it will use ``list`` - semantics and pop tokens from the list of parsed tokens. If passed - a non-integer argument (most likely a string), it will use ``dict`` - semantics and pop the corresponding value from any defined results - names. A second default return value argument is supported, just as in - ``dict.pop()``. - - Example:: - - def remove_first(tokens): - tokens.pop(0) - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321'] - - label = Word(alphas) - patt = label("LABEL") + OneOrMore(Word(nums)) - print(patt.parseString("AAB 123 321").dump()) - - # Use pop() in a parse action to remove named result (note that corresponding value is not - # removed from list form of results) - def remove_LABEL(tokens): - tokens.pop("LABEL") - return tokens - patt.addParseAction(remove_LABEL) - print(patt.parseString("AAB 123 321").dump()) - - prints:: - - ['AAB', '123', '321'] - - LABEL: AAB - - ['AAB', '123', '321'] - """ - if not args: - args = [-1] - for k, v in kwargs.items(): - if k == 'default': - args = (args[0], v) - else: - raise TypeError("pop() got an unexpected keyword argument '%s'" % k) - if (isinstance(args[0], int) - or len(args) == 1 - or args[0] in self): - index = args[0] - ret = self[index] - del self[index] - return ret - else: - defaultvalue = args[1] - return defaultvalue - - def get(self, key, defaultValue=None): - """ - Returns named result matching the given key, or if there is no - such name, then returns the given ``defaultValue`` or ``None`` if no - ``defaultValue`` is specified. - - Similar to ``dict.get()``. - - Example:: - - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString("1999/12/31") - print(result.get("year")) # -> '1999' - print(result.get("hour", "not specified")) # -> 'not specified' - print(result.get("hour")) # -> None - """ - if key in self: - return self[key] - else: - return defaultValue - - def insert(self, index, insStr): - """ - Inserts new element at location index in the list of parsed tokens. - - Similar to ``list.insert()``. - - Example:: - - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - - # use a parse action to insert the parse location in the front of the parsed results - def insert_locn(locn, tokens): - tokens.insert(0, locn) - print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321'] - """ - self.__toklist.insert(index, insStr) - # fixup indices in token dictionary - for name, occurrences in self.__tokdict.items(): - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - - def append(self, item): - """ - Add single element to end of ParseResults list of elements. - - Example:: - - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - - # use a parse action to compute the sum of the parsed integers, and add it to the end - def append_sum(tokens): - tokens.append(sum(map(int, tokens))) - print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444] - """ - self.__toklist.append(item) - - def extend(self, itemseq): - """ - Add sequence of elements to end of ParseResults list of elements. - - Example:: - - patt = OneOrMore(Word(alphas)) - - # use a parse action to append the reverse of the matched strings, to make a palindrome - def make_palindrome(tokens): - tokens.extend(reversed([t[::-1] for t in tokens])) - return ''.join(tokens) - print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' - """ - if isinstance(itemseq, ParseResults): - self.__iadd__(itemseq) - else: - self.__toklist.extend(itemseq) - - def clear(self): - """ - Clear all elements and results names. - """ - del self.__toklist[:] - self.__tokdict.clear() - - def __getattr__(self, name): - try: - return self[name] - except KeyError: - return "" - - def __add__(self, other): - ret = self.copy() - ret += other - return ret - - def __iadd__(self, other): - if other.__tokdict: - offset = len(self.__toklist) - addoffset = lambda a: offset if a < 0 else a + offset - otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) - for k, vlist in otheritems for v in vlist] - for k, v in otherdictitems: - self[k] = v - if isinstance(v[0], ParseResults): - v[0].__parent = wkref(self) - - self.__toklist += other.__toklist - self.__accumNames.update(other.__accumNames) - return self - - def __radd__(self, other): - if isinstance(other, int) and other == 0: - # useful for merging many ParseResults using sum() builtin - return self.copy() - else: - # this may raise a TypeError - so be it - return other + self - - def __repr__(self): - return "(%s, %s)" % (repr(self.__toklist), repr(self.__tokdict)) - - def __str__(self): - return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']' - - def _asStringList(self, sep=''): - out = [] - for item in self.__toklist: - if out and sep: - out.append(sep) - if isinstance(item, ParseResults): - out += item._asStringList() - else: - out.append(_ustr(item)) - return out - - def asList(self): - """ - Returns the parse results as a nested list of matching tokens, all converted to strings. - - Example:: - - patt = OneOrMore(Word(alphas)) - result = patt.parseString("sldkj lsdkj sldkj") - # even though the result prints in string-like form, it is actually a pyparsing ParseResults - print(type(result), result) # -> ['sldkj', 'lsdkj', 'sldkj'] - - # Use asList() to create an actual list - result_list = result.asList() - print(type(result_list), result_list) # -> ['sldkj', 'lsdkj', 'sldkj'] - """ - return [res.asList() if isinstance(res, ParseResults) else res for res in self.__toklist] - - def asDict(self): - """ - Returns the named parse results as a nested dictionary. - - Example:: - - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString('12/31/1999') - print(type(result), repr(result)) # -> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) - - result_dict = result.asDict() - print(type(result_dict), repr(result_dict)) # -> {'day': '1999', 'year': '12', 'month': '31'} - - # even though a ParseResults supports dict-like access, sometime you just need to have a dict - import json - print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable - print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"} - """ - if PY_3: - item_fn = self.items - else: - item_fn = self.iteritems - - def toItem(obj): - if isinstance(obj, ParseResults): - if obj.haskeys(): - return obj.asDict() - else: - return [toItem(v) for v in obj] - else: - return obj - - return dict((k, toItem(v)) for k, v in item_fn()) - - def copy(self): - """ - Returns a new copy of a :class:`ParseResults` object. - """ - ret = ParseResults(self.__toklist) - ret.__tokdict = dict(self.__tokdict.items()) - ret.__parent = self.__parent - ret.__accumNames.update(self.__accumNames) - ret.__name = self.__name - return ret - - def asXML(self, doctag=None, namedItemsOnly=False, indent="", formatted=True): - """ - (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names. - """ - nl = "\n" - out = [] - namedItems = dict((v[1], k) for (k, vlist) in self.__tokdict.items() - for v in vlist) - nextLevelIndent = indent + " " - - # collapse out indents if formatting is not desired - if not formatted: - indent = "" - nextLevelIndent = "" - nl = "" - - selfTag = None - if doctag is not None: - selfTag = doctag - else: - if self.__name: - selfTag = self.__name - - if not selfTag: - if namedItemsOnly: - return "" - else: - selfTag = "ITEM" - - out += [nl, indent, "<", selfTag, ">"] - - for i, res in enumerate(self.__toklist): - if isinstance(res, ParseResults): - if i in namedItems: - out += [res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - out += [res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - # individual token, see if there is a name for it - resTag = None - if i in namedItems: - resTag = namedItems[i] - if not resTag: - if namedItemsOnly: - continue - else: - resTag = "ITEM" - xmlBodyText = _xml_escape(_ustr(res)) - out += [nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - ""] - - out += [nl, indent, ""] - return "".join(out) - - def __lookup(self, sub): - for k, vlist in self.__tokdict.items(): - for v, loc in vlist: - if sub is v: - return k - return None - - def getName(self): - r""" - Returns the results name for this token expression. Useful when several - different expressions might match at a particular location. - - Example:: - - integer = Word(nums) - ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") - house_number_expr = Suppress('#') + Word(nums, alphanums) - user_data = (Group(house_number_expr)("house_number") - | Group(ssn_expr)("ssn") - | Group(integer)("age")) - user_info = OneOrMore(user_data) - - result = user_info.parseString("22 111-22-3333 #221B") - for item in result: - print(item.getName(), ':', item[0]) - - prints:: - - age : 22 - ssn : 111-22-3333 - house_number : 221B - """ - if self.__name: - return self.__name - elif self.__parent: - par = self.__parent() - if par: - return par.__lookup(self) - else: - return None - elif (len(self) == 1 - and len(self.__tokdict) == 1 - and next(iter(self.__tokdict.values()))[0][1] in (0, -1)): - return next(iter(self.__tokdict.keys())) - else: - return None - - def dump(self, indent='', full=True, include_list=True, _depth=0): - """ - Diagnostic method for listing out the contents of - a :class:`ParseResults`. Accepts an optional ``indent`` argument so - that this string can be embedded in a nested display of other data. - - Example:: - - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString('12/31/1999') - print(result.dump()) - - prints:: - - ['12', '/', '31', '/', '1999'] - - day: 1999 - - month: 31 - - year: 12 - """ - out = [] - NL = '\n' - if include_list: - out.append(indent + _ustr(self.asList())) - else: - out.append('') - - if full: - if self.haskeys(): - items = sorted((str(k), v) for k, v in self.items()) - for k, v in items: - if out: - out.append(NL) - out.append("%s%s- %s: " % (indent, (' ' * _depth), k)) - if isinstance(v, ParseResults): - if v: - out.append(v.dump(indent=indent, full=full, include_list=include_list, _depth=_depth + 1)) - else: - out.append(_ustr(v)) - else: - out.append(repr(v)) - elif any(isinstance(vv, ParseResults) for vv in self): - v = self - for i, vv in enumerate(v): - if isinstance(vv, ParseResults): - out.append("\n%s%s[%d]:\n%s%s%s" % (indent, - (' ' * (_depth)), - i, - indent, - (' ' * (_depth + 1)), - vv.dump(indent=indent, - full=full, - include_list=include_list, - _depth=_depth + 1))) - else: - out.append("\n%s%s[%d]:\n%s%s%s" % (indent, - (' ' * (_depth)), - i, - indent, - (' ' * (_depth + 1)), - _ustr(vv))) - - return "".join(out) - - def pprint(self, *args, **kwargs): - """ - Pretty-printer for parsed results as a list, using the - `pprint `_ module. - Accepts additional positional or keyword args as defined for - `pprint.pprint `_ . - - Example:: - - ident = Word(alphas, alphanums) - num = Word(nums) - func = Forward() - term = ident | num | Group('(' + func + ')') - func <<= ident + Group(Optional(delimitedList(term))) - result = func.parseString("fna a,b,(fnb c,d,200),100") - result.pprint(width=40) - - prints:: - - ['fna', - ['a', - 'b', - ['(', 'fnb', ['c', 'd', '200'], ')'], - '100']] - """ - pprint.pprint(self.asList(), *args, **kwargs) - - # add support for pickle protocol - def __getstate__(self): - return (self.__toklist, - (self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name)) - - def __setstate__(self, state): - self.__toklist = state[0] - self.__tokdict, par, inAccumNames, self.__name = state[1] - self.__accumNames = {} - self.__accumNames.update(inAccumNames) - if par is not None: - self.__parent = wkref(par) - else: - self.__parent = None - - def __getnewargs__(self): - return self.__toklist, self.__name, self.__asList, self.__modal - - def __dir__(self): - return dir(type(self)) + list(self.keys()) - - @classmethod - def from_dict(cls, other, name=None): - """ - Helper classmethod to construct a ParseResults from a dict, preserving the - name-value relations as results names. If an optional 'name' argument is - given, a nested ParseResults will be returned - """ - def is_iterable(obj): - try: - iter(obj) - except Exception: - return False - else: - if PY_3: - return not isinstance(obj, (str, bytes)) - else: - return not isinstance(obj, basestring) - - ret = cls([]) - for k, v in other.items(): - if isinstance(v, Mapping): - ret += cls.from_dict(v, name=k) - else: - ret += cls([v], name=k, asList=is_iterable(v)) - if name is not None: - ret = cls([ret], name=name) - return ret - -MutableMapping.register(ParseResults) - -def col (loc, strg): - """Returns current column within a string, counting newlines as line separators. - The first column is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See - :class:`ParserElement.parseString` for more - information on parsing strings containing ```` s, and suggested - methods to maintain a consistent view of the parsed string, the parse - location, and line and column positions within the parsed string. - """ - s = strg - return 1 if 0 < loc < len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) - -def lineno(loc, strg): - """Returns current line number within a string, counting newlines as line separators. - The first line is number 1. - - Note - the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See :class:`ParserElement.parseString` - for more information on parsing strings containing ```` s, and - suggested methods to maintain a consistent view of the parsed string, the - parse location, and line and column positions within the parsed string. - """ - return strg.count("\n", 0, loc) + 1 - -def line(loc, strg): - """Returns the line of text containing loc within a string, counting newlines as line separators. - """ - lastCR = strg.rfind("\n", 0, loc) - nextCR = strg.find("\n", loc) - if nextCR >= 0: - return strg[lastCR + 1:nextCR] - else: - return strg[lastCR + 1:] - -def _defaultStartDebugAction(instring, loc, expr): - print(("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % (lineno(loc, instring), col(loc, instring)))) - -def _defaultSuccessDebugAction(instring, startloc, endloc, expr, toks): - print("Matched " + _ustr(expr) + " -> " + str(toks.asList())) - -def _defaultExceptionDebugAction(instring, loc, expr, exc): - print("Exception raised:" + _ustr(exc)) - -def nullDebugAction(*args): - """'Do-nothing' debug action, to suppress debugging output during parsing.""" - pass - -# Only works on Python 3.x - nonlocal is toxic to Python 2 installs -#~ 'decorator to trim function calls to match the arity of the target' -#~ def _trim_arity(func, maxargs=3): - #~ if func in singleArgBuiltins: - #~ return lambda s,l,t: func(t) - #~ limit = 0 - #~ foundArity = False - #~ def wrapper(*args): - #~ nonlocal limit,foundArity - #~ while 1: - #~ try: - #~ ret = func(*args[limit:]) - #~ foundArity = True - #~ return ret - #~ except TypeError: - #~ if limit == maxargs or foundArity: - #~ raise - #~ limit += 1 - #~ continue - #~ return wrapper - -# this version is Python 2.x-3.x cross-compatible -'decorator to trim function calls to match the arity of the target' -def _trim_arity(func, maxargs=2): - if func in singleArgBuiltins: - return lambda s, l, t: func(t) - limit = [0] - foundArity = [False] - - # traceback return data structure changed in Py3.5 - normalize back to plain tuples - if system_version[:2] >= (3, 5): - def extract_stack(limit=0): - # special handling for Python 3.5.0 - extra deep call stack by 1 - offset = -3 if system_version == (3, 5, 0) else -2 - frame_summary = traceback.extract_stack(limit=-offset + limit - 1)[offset] - return [frame_summary[:2]] - def extract_tb(tb, limit=0): - frames = traceback.extract_tb(tb, limit=limit) - frame_summary = frames[-1] - return [frame_summary[:2]] - else: - extract_stack = traceback.extract_stack - extract_tb = traceback.extract_tb - - # synthesize what would be returned by traceback.extract_stack at the call to - # user's parse action 'func', so that we don't incur call penalty at parse time - - LINE_DIFF = 6 - # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND - # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! - this_line = extract_stack(limit=2)[-1] - pa_call_line_synth = (this_line[0], this_line[1] + LINE_DIFF) - - def wrapper(*args): - while 1: - try: - ret = func(*args[limit[0]:]) - foundArity[0] = True - return ret - except TypeError: - # re-raise TypeErrors if they did not come from our arity testing - if foundArity[0]: - raise - else: - try: - tb = sys.exc_info()[-1] - if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth: - raise - finally: - try: - del tb - except NameError: - pass - - if limit[0] <= maxargs: - limit[0] += 1 - continue - raise - - # copy func name to wrapper for sensible debug output - func_name = "" - try: - func_name = getattr(func, '__name__', - getattr(func, '__class__').__name__) - except Exception: - func_name = str(func) - wrapper.__name__ = func_name - - return wrapper - - -class ParserElement(object): - """Abstract base level parser element class.""" - DEFAULT_WHITE_CHARS = " \n\t\r" - verbose_stacktrace = False - - @staticmethod - def setDefaultWhitespaceChars(chars): - r""" - Overrides the default whitespace chars - - Example:: - - # default whitespace chars are space, and newline - OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] - - # change to just treat newline as significant - ParserElement.setDefaultWhitespaceChars(" \t") - OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def'] - """ - ParserElement.DEFAULT_WHITE_CHARS = chars - - @staticmethod - def inlineLiteralsUsing(cls): - """ - Set class to be used for inclusion of string literals into a parser. - - Example:: - - # default literal class used is Literal - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] - - - # change to Suppress - ParserElement.inlineLiteralsUsing(Suppress) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - date_str.parseString("1999/12/31") # -> ['1999', '12', '31'] - """ - ParserElement._literalStringClass = cls - - @classmethod - def _trim_traceback(cls, tb): - while tb.tb_next: - tb = tb.tb_next - return tb - - def __init__(self, savelist=False): - self.parseAction = list() - self.failAction = None - # ~ self.name = "" # don't define self.name, let subclasses try/except upcall - self.strRepr = None - self.resultsName = None - self.saveAsList = savelist - self.skipWhitespace = True - self.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS) - self.copyDefaultWhiteChars = True - self.mayReturnEmpty = False # used when checking for left-recursion - self.keepTabs = False - self.ignoreExprs = list() - self.debug = False - self.streamlined = False - self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index - self.errmsg = "" - self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = (None, None, None) # custom debug actions - self.re = None - self.callPreparse = True # used to avoid redundant calls to preParse - self.callDuringTry = False - - def copy(self): - """ - Make a copy of this :class:`ParserElement`. Useful for defining - different parse actions for the same parsing pattern, using copies of - the original parse element. - - Example:: - - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - integerK = integer.copy().addParseAction(lambda toks: toks[0] * 1024) + Suppress("K") - integerM = integer.copy().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") - - print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M")) - - prints:: - - [5120, 100, 655360, 268435456] - - Equivalent form of ``expr.copy()`` is just ``expr()``:: - - integerM = integer().addParseAction(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") - """ - cpy = copy.copy(self) - cpy.parseAction = self.parseAction[:] - cpy.ignoreExprs = self.ignoreExprs[:] - if self.copyDefaultWhiteChars: - cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - return cpy - - def setName(self, name): - """ - Define name for this expression, makes debugging and exception messages clearer. - - Example:: - - Word(nums).parseString("ABC") # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1) - Word(nums).setName("integer").parseString("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) - """ - self.name = name - self.errmsg = "Expected " + self.name - if __diag__.enable_debug_on_named_expressions: - self.setDebug() - return self - - def setResultsName(self, name, listAllMatches=False): - """ - Define name for referencing matching tokens as a nested attribute - of the returned parse results. - NOTE: this returns a *copy* of the original :class:`ParserElement` object; - this is so that the client can define a basic element, such as an - integer, and reference it in multiple places with different names. - - You can also set results names using the abbreviated syntax, - ``expr("name")`` in place of ``expr.setResultsName("name")`` - - see :class:`__call__`. - - Example:: - - date_str = (integer.setResultsName("year") + '/' - + integer.setResultsName("month") + '/' - + integer.setResultsName("day")) - - # equivalent form: - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - """ - return self._setResultsName(name, listAllMatches) - - def _setResultsName(self, name, listAllMatches=False): - newself = self.copy() - if name.endswith("*"): - name = name[:-1] - listAllMatches = True - newself.resultsName = name - newself.modalResults = not listAllMatches - return newself - - def setBreak(self, breakFlag=True): - """Method to invoke the Python pdb debugger when this element is - about to be parsed. Set ``breakFlag`` to True to enable, False to - disable. - """ - if breakFlag: - _parseMethod = self._parse - def breaker(instring, loc, doActions=True, callPreParse=True): - import pdb - # this call to pdb.set_trace() is intentional, not a checkin error - pdb.set_trace() - return _parseMethod(instring, loc, doActions, callPreParse) - breaker._originalParseMethod = _parseMethod - self._parse = breaker - else: - if hasattr(self._parse, "_originalParseMethod"): - self._parse = self._parse._originalParseMethod - return self - - def setParseAction(self, *fns, **kwargs): - """ - Define one or more actions to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as ``fn(s, loc, toks)`` , - ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where: - - - s = the original string being parsed (see note below) - - loc = the location of the matching substring - - toks = a list of the matched tokens, packaged as a :class:`ParseResults` object - - If the functions in fns modify the tokens, they can return them as the return - value from fn, and the modified list of tokens will replace the original. - Otherwise, fn does not need to return any value. - - If None is passed as the parse action, all previously added parse actions for this - expression are cleared. - - Optional keyword arguments: - - callDuringTry = (default= ``False``) indicate if parse action should be run during lookaheads and alternate testing - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See :class:`parseString for more - information on parsing strings containing ```` s, and suggested - methods to maintain a consistent view of the parsed string, the parse - location, and line and column positions within the parsed string. - - Example:: - - integer = Word(nums) - date_str = integer + '/' + integer + '/' + integer - - date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] - - # use parse action to convert to ints at parse time - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - date_str = integer + '/' + integer + '/' + integer - - # note that integer fields are now ints, not strings - date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31] - """ - if list(fns) == [None,]: - self.parseAction = [] - else: - if not all(callable(fn) for fn in fns): - raise TypeError("parse actions must be callable") - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = kwargs.get("callDuringTry", False) - return self - - def addParseAction(self, *fns, **kwargs): - """ - Add one or more parse actions to expression's list of parse actions. See :class:`setParseAction`. - - See examples in :class:`copy`. - """ - self.parseAction += list(map(_trim_arity, list(fns))) - self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) - return self - - def addCondition(self, *fns, **kwargs): - """Add a boolean predicate function to expression's list of parse actions. See - :class:`setParseAction` for function call signatures. Unlike ``setParseAction``, - functions passed to ``addCondition`` need to return boolean success/fail of the condition. - - Optional keyword arguments: - - message = define a custom message to be used in the raised exception - - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException - - Example:: - - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - year_int = integer.copy() - year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") - date_str = year_int + '/' + integer + '/' + integer - - result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1) - """ - for fn in fns: - self.parseAction.append(conditionAsParseAction(fn, message=kwargs.get('message'), - fatal=kwargs.get('fatal', False))) - - self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) - return self - - def setFailAction(self, fn): - """Define action to perform if parsing fails at this expression. - Fail acton fn is a callable function that takes the arguments - ``fn(s, loc, expr, err)`` where: - - s = string being parsed - - loc = location where expression match was attempted and failed - - expr = the parse expression that failed - - err = the exception thrown - The function returns no value. It may throw :class:`ParseFatalException` - if it is desired to stop parsing immediately.""" - self.failAction = fn - return self - - def _skipIgnorables(self, instring, loc): - exprsFound = True - while exprsFound: - exprsFound = False - for e in self.ignoreExprs: - try: - while 1: - loc, dummy = e._parse(instring, loc) - exprsFound = True - except ParseException: - pass - return loc - - def preParse(self, instring, loc): - if self.ignoreExprs: - loc = self._skipIgnorables(instring, loc) - - if self.skipWhitespace: - wt = self.whiteChars - instrlen = len(instring) - while loc < instrlen and instring[loc] in wt: - loc += 1 - - return loc - - def parseImpl(self, instring, loc, doActions=True): - return loc, [] - - def postParse(self, instring, loc, tokenlist): - return tokenlist - - # ~ @profile - def _parseNoCache(self, instring, loc, doActions=True, callPreParse=True): - TRY, MATCH, FAIL = 0, 1, 2 - debugging = (self.debug) # and doActions) - - if debugging or self.failAction: - # ~ print ("Match", self, "at loc", loc, "(%d, %d)" % (lineno(loc, instring), col(loc, instring))) - if self.debugActions[TRY]: - self.debugActions[TRY](instring, loc, self) - try: - if callPreParse and self.callPreparse: - preloc = self.preParse(instring, loc) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or preloc >= len(instring): - try: - loc, tokens = self.parseImpl(instring, preloc, doActions) - except IndexError: - raise ParseException(instring, len(instring), self.errmsg, self) - else: - loc, tokens = self.parseImpl(instring, preloc, doActions) - except Exception as err: - # ~ print ("Exception raised:", err) - if self.debugActions[FAIL]: - self.debugActions[FAIL](instring, tokensStart, self, err) - if self.failAction: - self.failAction(instring, tokensStart, self, err) - raise - else: - if callPreParse and self.callPreparse: - preloc = self.preParse(instring, loc) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or preloc >= len(instring): - try: - loc, tokens = self.parseImpl(instring, preloc, doActions) - except IndexError: - raise ParseException(instring, len(instring), self.errmsg, self) - else: - loc, tokens = self.parseImpl(instring, preloc, doActions) - - tokens = self.postParse(instring, loc, tokens) - - retTokens = ParseResults(tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults) - if self.parseAction and (doActions or self.callDuringTry): - if debugging: - try: - for fn in self.parseAction: - try: - tokens = fn(instring, tokensStart, retTokens) - except IndexError as parse_action_exc: - exc = ParseException("exception raised in parse action") - exc.__cause__ = parse_action_exc - raise exc - - if tokens is not None and tokens is not retTokens: - retTokens = ParseResults(tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens, (ParseResults, list)), - modal=self.modalResults) - except Exception as err: - # ~ print "Exception raised in user parse action:", err - if self.debugActions[FAIL]: - self.debugActions[FAIL](instring, tokensStart, self, err) - raise - else: - for fn in self.parseAction: - try: - tokens = fn(instring, tokensStart, retTokens) - except IndexError as parse_action_exc: - exc = ParseException("exception raised in parse action") - exc.__cause__ = parse_action_exc - raise exc - - if tokens is not None and tokens is not retTokens: - retTokens = ParseResults(tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens, (ParseResults, list)), - modal=self.modalResults) - if debugging: - # ~ print ("Matched", self, "->", retTokens.asList()) - if self.debugActions[MATCH]: - self.debugActions[MATCH](instring, tokensStart, loc, self, retTokens) - - return loc, retTokens - - def tryParse(self, instring, loc): - try: - return self._parse(instring, loc, doActions=False)[0] - except ParseFatalException: - raise ParseException(instring, loc, self.errmsg, self) - - def canParseNext(self, instring, loc): - try: - self.tryParse(instring, loc) - except (ParseException, IndexError): - return False - else: - return True - - class _UnboundedCache(object): - def __init__(self): - cache = {} - self.not_in_cache = not_in_cache = object() - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - - def clear(self): - cache.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - if _OrderedDict is not None: - class _FifoCache(object): - def __init__(self, size): - self.not_in_cache = not_in_cache = object() - - cache = _OrderedDict() - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - while len(cache) > size: - try: - cache.popitem(False) - except KeyError: - pass - - def clear(self): - cache.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - else: - class _FifoCache(object): - def __init__(self, size): - self.not_in_cache = not_in_cache = object() - - cache = {} - key_fifo = collections.deque([], size) - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - while len(key_fifo) > size: - cache.pop(key_fifo.popleft(), None) - key_fifo.append(key) - - def clear(self): - cache.clear() - key_fifo.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - # argument cache for optimizing repeated calls when backtracking through recursive expressions - packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail - packrat_cache_lock = RLock() - packrat_cache_stats = [0, 0] - - # this method gets repeatedly called during backtracking with the same arguments - - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache(self, instring, loc, doActions=True, callPreParse=True): - HIT, MISS = 0, 1 - lookup = (self, instring, loc, callPreParse, doActions) - with ParserElement.packrat_cache_lock: - cache = ParserElement.packrat_cache - value = cache.get(lookup) - if value is cache.not_in_cache: - ParserElement.packrat_cache_stats[MISS] += 1 - try: - value = self._parseNoCache(instring, loc, doActions, callPreParse) - except ParseBaseException as pe: - # cache a copy of the exception, without the traceback - cache.set(lookup, pe.__class__(*pe.args)) - raise - else: - cache.set(lookup, (value[0], value[1].copy())) - return value - else: - ParserElement.packrat_cache_stats[HIT] += 1 - if isinstance(value, Exception): - raise value - return value[0], value[1].copy() - - _parse = _parseNoCache - - @staticmethod - def resetCache(): - ParserElement.packrat_cache.clear() - ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats) - - _packratEnabled = False - @staticmethod - def enablePackrat(cache_size_limit=128): - """Enables "packrat" parsing, which adds memoizing to the parsing logic. - Repeated parse attempts at the same string location (which happens - often in many complex grammars) can immediately return a cached value, - instead of re-executing parsing/validating code. Memoizing is done of - both valid results and parsing exceptions. - - Parameters: - - - cache_size_limit - (default= ``128``) - if an integer value is provided - will limit the size of the packrat cache; if None is passed, then - the cache size will be unbounded; if 0 is passed, the cache will - be effectively disabled. - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method :class:`ParserElement.enablePackrat`. - For best results, call ``enablePackrat()`` immediately after - importing pyparsing. - - Example:: - - import pyparsing - pyparsing.ParserElement.enablePackrat() - """ - if not ParserElement._packratEnabled: - ParserElement._packratEnabled = True - if cache_size_limit is None: - ParserElement.packrat_cache = ParserElement._UnboundedCache() - else: - ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit) - ParserElement._parse = ParserElement._parseCache - - def parseString(self, instring, parseAll=False): - """ - Execute the parse expression with the given string. - This is the main interface to the client code, once the complete - expression has been built. - - Returns the parsed data as a :class:`ParseResults` object, which may be - accessed as a list, or as a dict or object with attributes if the given parser - includes results names. - - If you want the grammar to require that the entire input string be - successfully parsed, then set ``parseAll`` to True (equivalent to ending - the grammar with ``StringEnd()``). - - Note: ``parseString`` implicitly calls ``expandtabs()`` on the input string, - in order to report proper column numbers in parse actions. - If the input string contains tabs and - the grammar uses parse actions that use the ``loc`` argument to index into the - string being parsed, you can ensure you have a consistent view of the input - string by: - - - calling ``parseWithTabs`` on your grammar before calling ``parseString`` - (see :class:`parseWithTabs`) - - define your parse action using the full ``(s, loc, toks)`` signature, and - reference the input string using the parse action's ``s`` argument - - explictly expand the tabs in your input string before calling - ``parseString`` - - Example:: - - Word('a').parseString('aaaaabaaa') # -> ['aaaaa'] - Word('a').parseString('aaaaabaaa', parseAll=True) # -> Exception: Expected end of text - """ - ParserElement.resetCache() - if not self.streamlined: - self.streamline() - # ~ self.saveAsList = True - for e in self.ignoreExprs: - e.streamline() - if not self.keepTabs: - instring = instring.expandtabs() - try: - loc, tokens = self._parse(instring, 0) - if parseAll: - loc = self.preParse(instring, loc) - se = Empty() + StringEnd() - se._parse(instring, loc) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clearing out pyparsing internal stack trace - if getattr(exc, '__traceback__', None) is not None: - exc.__traceback__ = self._trim_traceback(exc.__traceback__) - raise exc - else: - return tokens - - def scanString(self, instring, maxMatches=_MAX_INT, overlap=False): - """ - Scan the input string for expression matches. Each match will return the - matching tokens, start location, and end location. May be called with optional - ``maxMatches`` argument, to clip scanning after 'n' matches are found. If - ``overlap`` is specified, then overlapping matches will be reported. - - Note that the start and end locations are reported relative to the string - being parsed. See :class:`parseString` for more information on parsing - strings with embedded tabs. - - Example:: - - source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" - print(source) - for tokens, start, end in Word(alphas).scanString(source): - print(' '*start + '^'*(end-start)) - print(' '*start + tokens[0]) - - prints:: - - sldjf123lsdjjkf345sldkjf879lkjsfd987 - ^^^^^ - sldjf - ^^^^^^^ - lsdjjkf - ^^^^^^ - sldkjf - ^^^^^^ - lkjsfd - """ - if not self.streamlined: - self.streamline() - for e in self.ignoreExprs: - e.streamline() - - if not self.keepTabs: - instring = _ustr(instring).expandtabs() - instrlen = len(instring) - loc = 0 - preparseFn = self.preParse - parseFn = self._parse - ParserElement.resetCache() - matches = 0 - try: - while loc <= instrlen and matches < maxMatches: - try: - preloc = preparseFn(instring, loc) - nextLoc, tokens = parseFn(instring, preloc, callPreParse=False) - except ParseException: - loc = preloc + 1 - else: - if nextLoc > loc: - matches += 1 - yield tokens, preloc, nextLoc - if overlap: - nextloc = preparseFn(instring, loc) - if nextloc > loc: - loc = nextLoc - else: - loc += 1 - else: - loc = nextLoc - else: - loc = preloc + 1 - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clearing out pyparsing internal stack trace - if getattr(exc, '__traceback__', None) is not None: - exc.__traceback__ = self._trim_traceback(exc.__traceback__) - raise exc - - def transformString(self, instring): - """ - Extension to :class:`scanString`, to modify matching text with modified tokens that may - be returned from a parse action. To use ``transformString``, define a grammar and - attach a parse action to it that modifies the returned token list. - Invoking ``transformString()`` on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. ``transformString()`` returns the resulting transformed string. - - Example:: - - wd = Word(alphas) - wd.setParseAction(lambda toks: toks[0].title()) - - print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york.")) - - prints:: - - Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York. - """ - out = [] - lastE = 0 - # force preservation of s, to minimize unwanted transformation of string, and to - # keep string locs straight between transformString and scanString - self.keepTabs = True - try: - for t, s, e in self.scanString(instring): - out.append(instring[lastE:s]) - if t: - if isinstance(t, ParseResults): - out += t.asList() - elif isinstance(t, list): - out += t - else: - out.append(t) - lastE = e - out.append(instring[lastE:]) - out = [o for o in out if o] - return "".join(map(_ustr, _flatten(out))) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clearing out pyparsing internal stack trace - if getattr(exc, '__traceback__', None) is not None: - exc.__traceback__ = self._trim_traceback(exc.__traceback__) - raise exc - - def searchString(self, instring, maxMatches=_MAX_INT): - """ - Another extension to :class:`scanString`, simplifying the access to the tokens found - to match the given parse expression. May be called with optional - ``maxMatches`` argument, to clip searching after 'n' matches are found. - - Example:: - - # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters - cap_word = Word(alphas.upper(), alphas.lower()) - - print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")) - - # the sum() builtin can be used to merge results into a single ParseResults object - print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))) - - prints:: - - [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] - ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] - """ - try: - return ParseResults([t for t, s, e in self.scanString(instring, maxMatches)]) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clearing out pyparsing internal stack trace - if getattr(exc, '__traceback__', None) is not None: - exc.__traceback__ = self._trim_traceback(exc.__traceback__) - raise exc - - def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): - """ - Generator method to split a string using the given expression as a separator. - May be called with optional ``maxsplit`` argument, to limit the number of splits; - and the optional ``includeSeparators`` argument (default= ``False``), if the separating - matching text should be included in the split results. - - Example:: - - punc = oneOf(list(".,;:/-!?")) - print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) - - prints:: - - ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] - """ - splits = 0 - last = 0 - for t, s, e in self.scanString(instring, maxMatches=maxsplit): - yield instring[last:s] - if includeSeparators: - yield t[0] - last = e - yield instring[last:] - - def __add__(self, other): - """ - Implementation of + operator - returns :class:`And`. Adding strings to a ParserElement - converts them to :class:`Literal`s by default. - - Example:: - - greet = Word(alphas) + "," + Word(alphas) + "!" - hello = "Hello, World!" - print (hello, "->", greet.parseString(hello)) - - prints:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - - ``...`` may be used as a parse expression as a short form of :class:`SkipTo`. - - Literal('start') + ... + Literal('end') - - is equivalent to: - - Literal('start') + SkipTo('end')("_skipped*") + Literal('end') - - Note that the skipped text is returned with '_skipped' as a results name, - and to support having multiple skips in the same parser, the value returned is - a list of all skipped text. - """ - if other is Ellipsis: - return _PendingSkip(self) - - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And([self, other]) - - def __radd__(self, other): - """ - Implementation of + operator when left operand is not a :class:`ParserElement` - """ - if other is Ellipsis: - return SkipTo(self)("_skipped*") + self - - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other + self - - def __sub__(self, other): - """ - Implementation of - operator, returns :class:`And` with error stop - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return self + And._ErrorStop() + other - - def __rsub__(self, other): - """ - Implementation of - operator when left operand is not a :class:`ParserElement` - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other - self - - def __mul__(self, other): - """ - Implementation of * operator, allows use of ``expr * 3`` in place of - ``expr + expr + expr``. Expressions may also me multiplied by a 2-integer - tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples - may also include ``None`` as in: - - ``expr*(n, None)`` or ``expr*(n, )`` is equivalent - to ``expr*n + ZeroOrMore(expr)`` - (read as "at least n instances of ``expr``") - - ``expr*(None, n)`` is equivalent to ``expr*(0, n)`` - (read as "0 to n instances of ``expr``") - - ``expr*(None, None)`` is equivalent to ``ZeroOrMore(expr)`` - - ``expr*(1, None)`` is equivalent to ``OneOrMore(expr)`` - - Note that ``expr*(None, n)`` does not raise an exception if - more than n exprs exist in the input stream; that is, - ``expr*(None, n)`` does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - ``expr*(None, n) + ~expr`` - """ - if other is Ellipsis: - other = (0, None) - elif isinstance(other, tuple) and other[:1] == (Ellipsis,): - other = ((0, ) + other[1:] + (None,))[:2] - - if isinstance(other, int): - minElements, optElements = other, 0 - elif isinstance(other, tuple): - other = tuple(o if o is not Ellipsis else None for o in other) - other = (other + (None, None))[:2] - if other[0] is None: - other = (0, other[1]) - if isinstance(other[0], int) and other[1] is None: - if other[0] == 0: - return ZeroOrMore(self) - if other[0] == 1: - return OneOrMore(self) - else: - return self * other[0] + ZeroOrMore(self) - elif isinstance(other[0], int) and isinstance(other[1], int): - minElements, optElements = other - optElements -= minElements - else: - raise TypeError("cannot multiply 'ParserElement' and ('%s', '%s') objects", type(other[0]), type(other[1])) - else: - raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) - - if minElements < 0: - raise ValueError("cannot multiply ParserElement by negative value") - if optElements < 0: - raise ValueError("second tuple value must be greater or equal to first tuple value") - if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0, 0)") - - if optElements: - def makeOptionalList(n): - if n > 1: - return Optional(self + makeOptionalList(n - 1)) - else: - return Optional(self) - if minElements: - if minElements == 1: - ret = self + makeOptionalList(optElements) - else: - ret = And([self] * minElements) + makeOptionalList(optElements) - else: - ret = makeOptionalList(optElements) - else: - if minElements == 1: - ret = self - else: - ret = And([self] * minElements) - return ret - - def __rmul__(self, other): - return self.__mul__(other) - - def __or__(self, other): - """ - Implementation of | operator - returns :class:`MatchFirst` - """ - if other is Ellipsis: - return _PendingSkip(self, must_skip=True) - - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst([self, other]) - - def __ror__(self, other): - """ - Implementation of | operator when left operand is not a :class:`ParserElement` - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other | self - - def __xor__(self, other): - """ - Implementation of ^ operator - returns :class:`Or` - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Or([self, other]) - - def __rxor__(self, other): - """ - Implementation of ^ operator when left operand is not a :class:`ParserElement` - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other ^ self - - def __and__(self, other): - """ - Implementation of & operator - returns :class:`Each` - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Each([self, other]) - - def __rand__(self, other): - """ - Implementation of & operator when left operand is not a :class:`ParserElement` - """ - if isinstance(other, basestring): - other = self._literalStringClass(other) - if not isinstance(other, ParserElement): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other & self - - def __invert__(self): - """ - Implementation of ~ operator - returns :class:`NotAny` - """ - return NotAny(self) - - def __iter__(self): - # must implement __iter__ to override legacy use of sequential access to __getitem__ to - # iterate over a sequence - raise TypeError('%r object is not iterable' % self.__class__.__name__) - - def __getitem__(self, key): - """ - use ``[]`` indexing notation as a short form for expression repetition: - - ``expr[n]`` is equivalent to ``expr*n`` - - ``expr[m, n]`` is equivalent to ``expr*(m, n)`` - - ``expr[n, ...]`` or ``expr[n,]`` is equivalent - to ``expr*n + ZeroOrMore(expr)`` - (read as "at least n instances of ``expr``") - - ``expr[..., n]`` is equivalent to ``expr*(0, n)`` - (read as "0 to n instances of ``expr``") - - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)`` - - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)`` - ``None`` may be used in place of ``...``. - - Note that ``expr[..., n]`` and ``expr[m, n]``do not raise an exception - if more than ``n`` ``expr``s exist in the input stream. If this behavior is - desired, then write ``expr[..., n] + ~expr``. - """ - - # convert single arg keys to tuples - try: - if isinstance(key, str): - key = (key,) - iter(key) - except TypeError: - key = (key, key) - - if len(key) > 2: - warnings.warn("only 1 or 2 index arguments supported ({0}{1})".format(key[:5], - '... [{0}]'.format(len(key)) - if len(key) > 5 else '')) - - # clip to 2 elements - ret = self * tuple(key[:2]) - return ret - - def __call__(self, name=None): - """ - Shortcut for :class:`setResultsName`, with ``listAllMatches=False``. - - If ``name`` is given with a trailing ``'*'`` character, then ``listAllMatches`` will be - passed as ``True``. - - If ``name` is omitted, same as calling :class:`copy`. - - Example:: - - # these are equivalent - userdata = Word(alphas).setResultsName("name") + Word(nums + "-").setResultsName("socsecno") - userdata = Word(alphas)("name") + Word(nums + "-")("socsecno") - """ - if name is not None: - return self._setResultsName(name) - else: - return self.copy() - - def suppress(self): - """ - Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from - cluttering up returned output. - """ - return Suppress(self) - - def leaveWhitespace(self): - """ - Disables the skipping of whitespace before matching the characters in the - :class:`ParserElement`'s defined pattern. This is normally only used internally by - the pyparsing module, but may be needed in some whitespace-sensitive grammars. - """ - self.skipWhitespace = False - return self - - def setWhitespaceChars(self, chars): - """ - Overrides the default whitespace chars - """ - self.skipWhitespace = True - self.whiteChars = chars - self.copyDefaultWhiteChars = False - return self - - def parseWithTabs(self): - """ - Overrides default behavior to expand ````s to spaces before parsing the input string. - Must be called before ``parseString`` when the input grammar contains elements that - match ```` characters. - """ - self.keepTabs = True - return self - - def ignore(self, other): - """ - Define expression to be ignored (e.g., comments) while doing pattern - matching; may be called repeatedly, to define multiple comment or other - ignorable patterns. - - Example:: - - patt = OneOrMore(Word(alphas)) - patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj'] - - patt.ignore(cStyleComment) - patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd'] - """ - if isinstance(other, basestring): - other = Suppress(other) - - if isinstance(other, Suppress): - if other not in self.ignoreExprs: - self.ignoreExprs.append(other) - else: - self.ignoreExprs.append(Suppress(other.copy())) - return self - - def setDebugActions(self, startAction, successAction, exceptionAction): - """ - Enable display of debugging messages while doing pattern matching. - """ - self.debugActions = (startAction or _defaultStartDebugAction, - successAction or _defaultSuccessDebugAction, - exceptionAction or _defaultExceptionDebugAction) - self.debug = True - return self - - def setDebug(self, flag=True): - """ - Enable display of debugging messages while doing pattern matching. - Set ``flag`` to True to enable, False to disable. - - Example:: - - wd = Word(alphas).setName("alphaword") - integer = Word(nums).setName("numword") - term = wd | integer - - # turn on debugging for wd - wd.setDebug() - - OneOrMore(term).parseString("abc 123 xyz 890") - - prints:: - - Match alphaword at loc 0(1,1) - Matched alphaword -> ['abc'] - Match alphaword at loc 3(1,4) - Exception raised:Expected alphaword (at char 4), (line:1, col:5) - Match alphaword at loc 7(1,8) - Matched alphaword -> ['xyz'] - Match alphaword at loc 11(1,12) - Exception raised:Expected alphaword (at char 12), (line:1, col:13) - Match alphaword at loc 15(1,16) - Exception raised:Expected alphaword (at char 15), (line:1, col:16) - - The output shown is that produced by the default debug actions - custom debug actions can be - specified using :class:`setDebugActions`. Prior to attempting - to match the ``wd`` expression, the debugging message ``"Match at loc (,)"`` - is shown. Then if the parse succeeds, a ``"Matched"`` message is shown, or an ``"Exception raised"`` - message is shown. Also note the use of :class:`setName` to assign a human-readable name to the expression, - which makes debugging and exception messages easier to understand - for instance, the default - name created for the :class:`Word` expression without calling ``setName`` is ``"W:(ABCD...)"``. - """ - if flag: - self.setDebugActions(_defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction) - else: - self.debug = False - return self - - def __str__(self): - return self.name - - def __repr__(self): - return _ustr(self) - - def streamline(self): - self.streamlined = True - self.strRepr = None - return self - - def checkRecursion(self, parseElementList): - pass - - def validate(self, validateTrace=None): - """ - Check defined expressions for valid structure, check for infinite recursive definitions. - """ - self.checkRecursion([]) - - def parseFile(self, file_or_filename, parseAll=False): - """ - Execute the parse expression on the given file or filename. - If a filename is specified (instead of a file object), - the entire file is opened, read, and closed before parsing. - """ - try: - file_contents = file_or_filename.read() - except AttributeError: - with open(file_or_filename, "r") as f: - file_contents = f.read() - try: - return self.parseString(file_contents, parseAll) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clearing out pyparsing internal stack trace - if getattr(exc, '__traceback__', None) is not None: - exc.__traceback__ = self._trim_traceback(exc.__traceback__) - raise exc - - def __eq__(self, other): - if self is other: - return True - elif isinstance(other, basestring): - return self.matches(other) - elif isinstance(other, ParserElement): - return vars(self) == vars(other) - return False - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return id(self) - - def __req__(self, other): - return self == other - - def __rne__(self, other): - return not (self == other) - - def matches(self, testString, parseAll=True): - """ - Method for quick testing of a parser against a test string. Good for simple - inline microtests of sub expressions while building up larger parser. - - Parameters: - - testString - to test against this expression for a match - - parseAll - (default= ``True``) - flag to pass to :class:`parseString` when running tests - - Example:: - - expr = Word(nums) - assert expr.matches("100") - """ - try: - self.parseString(_ustr(testString), parseAll=parseAll) - return True - except ParseBaseException: - return False - - def runTests(self, tests, parseAll=True, comment='#', - fullDump=True, printResults=True, failureTests=False, postParse=None, - file=None): - """ - Execute the parse expression on a series of test strings, showing each - test, the parsed results or where the parse failed. Quick and easy way to - run a parse expression against a list of sample strings. - - Parameters: - - tests - a list of separate test strings, or a multiline string of test strings - - parseAll - (default= ``True``) - flag to pass to :class:`parseString` when running tests - - comment - (default= ``'#'``) - expression for indicating embedded comments in the test - string; pass None to disable comment filtering - - fullDump - (default= ``True``) - dump results as list followed by results names in nested outline; - if False, only dump nested list - - printResults - (default= ``True``) prints test output to stdout - - failureTests - (default= ``False``) indicates if these tests are expected to fail parsing - - postParse - (default= ``None``) optional callback for successful parse results; called as - `fn(test_string, parse_results)` and returns a string to be added to the test output - - file - (default=``None``) optional file-like object to which test output will be written; - if None, will default to ``sys.stdout`` - - Returns: a (success, results) tuple, where success indicates that all tests succeeded - (or failed if ``failureTests`` is True), and the results contain a list of lines of each - test's output - - Example:: - - number_expr = pyparsing_common.number.copy() - - result = number_expr.runTests(''' - # unsigned integer - 100 - # negative integer - -100 - # float with scientific notation - 6.02e23 - # integer with scientific notation - 1e-12 - ''') - print("Success" if result[0] else "Failed!") - - result = number_expr.runTests(''' - # stray character - 100Z - # missing leading digit before '.' - -.100 - # too many '.' - 3.14.159 - ''', failureTests=True) - print("Success" if result[0] else "Failed!") - - prints:: - - # unsigned integer - 100 - [100] - - # negative integer - -100 - [-100] - - # float with scientific notation - 6.02e23 - [6.02e+23] - - # integer with scientific notation - 1e-12 - [1e-12] - - Success - - # stray character - 100Z - ^ - FAIL: Expected end of text (at char 3), (line:1, col:4) - - # missing leading digit before '.' - -.100 - ^ - FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1) - - # too many '.' - 3.14.159 - ^ - FAIL: Expected end of text (at char 4), (line:1, col:5) - - Success - - Each test string must be on a single line. If you want to test a string that spans multiple - lines, create a test like this:: - - expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines") - - (Note that this is a raw string literal, you must include the leading 'r'.) - """ - if isinstance(tests, basestring): - tests = list(map(str.strip, tests.rstrip().splitlines())) - if isinstance(comment, basestring): - comment = Literal(comment) - if file is None: - file = sys.stdout - print_ = file.write - - allResults = [] - comments = [] - success = True - NL = Literal(r'\n').addParseAction(replaceWith('\n')).ignore(quotedString) - BOM = u'\ufeff' - for t in tests: - if comment is not None and comment.matches(t, False) or comments and not t: - comments.append(t) - continue - if not t: - continue - out = ['\n' + '\n'.join(comments) if comments else '', t] - comments = [] - try: - # convert newline marks to actual newlines, and strip leading BOM if present - t = NL.transformString(t.lstrip(BOM)) - result = self.parseString(t, parseAll=parseAll) - except ParseBaseException as pe: - fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" - if '\n' in t: - out.append(line(pe.loc, t)) - out.append(' ' * (col(pe.loc, t) - 1) + '^' + fatal) - else: - out.append(' ' * pe.loc + '^' + fatal) - out.append("FAIL: " + str(pe)) - success = success and failureTests - result = pe - except Exception as exc: - out.append("FAIL-EXCEPTION: " + str(exc)) - success = success and failureTests - result = exc - else: - success = success and not failureTests - if postParse is not None: - try: - pp_value = postParse(t, result) - if pp_value is not None: - if isinstance(pp_value, ParseResults): - out.append(pp_value.dump()) - else: - out.append(str(pp_value)) - else: - out.append(result.dump()) - except Exception as e: - out.append(result.dump(full=fullDump)) - out.append("{0} failed: {1}: {2}".format(postParse.__name__, type(e).__name__, e)) - else: - out.append(result.dump(full=fullDump)) - - if printResults: - if fullDump: - out.append('') - print_('\n'.join(out)) - - allResults.append((t, result)) - - return success, allResults - - -class _PendingSkip(ParserElement): - # internal placeholder class to hold a place were '...' is added to a parser element, - # once another ParserElement is added, this placeholder will be replaced with a SkipTo - def __init__(self, expr, must_skip=False): - super(_PendingSkip, self).__init__() - self.strRepr = str(expr + Empty()).replace('Empty', '...') - self.name = self.strRepr - self.anchor = expr - self.must_skip = must_skip - - def __add__(self, other): - skipper = SkipTo(other).setName("...")("_skipped*") - if self.must_skip: - def must_skip(t): - if not t._skipped or t._skipped.asList() == ['']: - del t[0] - t.pop("_skipped", None) - def show_skip(t): - if t._skipped.asList()[-1:] == ['']: - skipped = t.pop('_skipped') - t['_skipped'] = 'missing <' + repr(self.anchor) + '>' - return (self.anchor + skipper().addParseAction(must_skip) - | skipper().addParseAction(show_skip)) + other - - return self.anchor + skipper + other - - def __repr__(self): - return self.strRepr - - def parseImpl(self, *args): - raise Exception("use of `...` expression without following SkipTo target expression") - - -class Token(ParserElement): - """Abstract :class:`ParserElement` subclass, for defining atomic - matching patterns. - """ - def __init__(self): - super(Token, self).__init__(savelist=False) - - -class Empty(Token): - """An empty token, will always match. - """ - def __init__(self): - super(Empty, self).__init__() - self.name = "Empty" - self.mayReturnEmpty = True - self.mayIndexError = False - - -class NoMatch(Token): - """A token that will never match. - """ - def __init__(self): - super(NoMatch, self).__init__() - self.name = "NoMatch" - self.mayReturnEmpty = True - self.mayIndexError = False - self.errmsg = "Unmatchable token" - - def parseImpl(self, instring, loc, doActions=True): - raise ParseException(instring, loc, self.errmsg, self) - - -class Literal(Token): - """Token to exactly match a specified string. - - Example:: - - Literal('blah').parseString('blah') # -> ['blah'] - Literal('blah').parseString('blahfooblah') # -> ['blah'] - Literal('blah').parseString('bla') # -> Exception: Expected "blah" - - For case-insensitive matching, use :class:`CaselessLiteral`. - - For keyword matching (force word break before and after the matched string), - use :class:`Keyword` or :class:`CaselessKeyword`. - """ - def __init__(self, matchString): - super(Literal, self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Literal; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.__class__ = Empty - self.name = '"%s"' % _ustr(self.match) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - - # Performance tuning: modify __class__ to select - # a parseImpl optimized for single-character check - if self.matchLen == 1 and type(self) is Literal: - self.__class__ = _SingleCharLiteral - - def parseImpl(self, instring, loc, doActions=True): - if instring[loc] == self.firstMatchChar and instring.startswith(self.match, loc): - return loc + self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - -class _SingleCharLiteral(Literal): - def parseImpl(self, instring, loc, doActions=True): - if instring[loc] == self.firstMatchChar: - return loc + 1, self.match - raise ParseException(instring, loc, self.errmsg, self) - -_L = Literal -ParserElement._literalStringClass = Literal - -class Keyword(Token): - """Token to exactly match a specified string as a keyword, that is, - it must be immediately followed by a non-keyword character. Compare - with :class:`Literal`: - - - ``Literal("if")`` will match the leading ``'if'`` in - ``'ifAndOnlyIf'``. - - ``Keyword("if")`` will not; it will only match the leading - ``'if'`` in ``'if x=1'``, or ``'if(y==2)'`` - - Accepts two optional constructor arguments in addition to the - keyword string: - - - ``identChars`` is a string of characters that would be valid - identifier characters, defaulting to all alphanumerics + "_" and - "$" - - ``caseless`` allows case-insensitive matching, default is ``False``. - - Example:: - - Keyword("start").parseString("start") # -> ['start'] - Keyword("start").parseString("starting") # -> Exception - - For case-insensitive matching, use :class:`CaselessKeyword`. - """ - DEFAULT_KEYWORD_CHARS = alphanums + "_$" - - def __init__(self, matchString, identChars=None, caseless=False): - super(Keyword, self).__init__() - if identChars is None: - identChars = Keyword.DEFAULT_KEYWORD_CHARS - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.name = '"%s"' % self.match - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - self.caseless = caseless - if caseless: - self.caselessmatch = matchString.upper() - identChars = identChars.upper() - self.identChars = set(identChars) - - def parseImpl(self, instring, loc, doActions=True): - if self.caseless: - if ((instring[loc:loc + self.matchLen].upper() == self.caselessmatch) - and (loc >= len(instring) - self.matchLen - or instring[loc + self.matchLen].upper() not in self.identChars) - and (loc == 0 - or instring[loc - 1].upper() not in self.identChars)): - return loc + self.matchLen, self.match - - else: - if instring[loc] == self.firstMatchChar: - if ((self.matchLen == 1 or instring.startswith(self.match, loc)) - and (loc >= len(instring) - self.matchLen - or instring[loc + self.matchLen] not in self.identChars) - and (loc == 0 or instring[loc - 1] not in self.identChars)): - return loc + self.matchLen, self.match - - raise ParseException(instring, loc, self.errmsg, self) - - def copy(self): - c = super(Keyword, self).copy() - c.identChars = Keyword.DEFAULT_KEYWORD_CHARS - return c - - @staticmethod - def setDefaultKeywordChars(chars): - """Overrides the default Keyword chars - """ - Keyword.DEFAULT_KEYWORD_CHARS = chars - -class CaselessLiteral(Literal): - """Token to match a specified string, ignoring case of letters. - Note: the matched results will always be in the case of the given - match string, NOT the case of the input text. - - Example:: - - OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD'] - - (Contrast with example for :class:`CaselessKeyword`.) - """ - def __init__(self, matchString): - super(CaselessLiteral, self).__init__(matchString.upper()) - # Preserve the defining literal. - self.returnString = matchString - self.name = "'%s'" % self.returnString - self.errmsg = "Expected " + self.name - - def parseImpl(self, instring, loc, doActions=True): - if instring[loc:loc + self.matchLen].upper() == self.match: - return loc + self.matchLen, self.returnString - raise ParseException(instring, loc, self.errmsg, self) - -class CaselessKeyword(Keyword): - """ - Caseless version of :class:`Keyword`. - - Example:: - - OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD'] - - (Contrast with example for :class:`CaselessLiteral`.) - """ - def __init__(self, matchString, identChars=None): - super(CaselessKeyword, self).__init__(matchString, identChars, caseless=True) - -class CloseMatch(Token): - """A variation on :class:`Literal` which matches "close" matches, - that is, strings with at most 'n' mismatching characters. - :class:`CloseMatch` takes parameters: - - - ``match_string`` - string to be matched - - ``maxMismatches`` - (``default=1``) maximum number of - mismatches allowed to count as a match - - The results from a successful parse will contain the matched text - from the input string and the following named results: - - - ``mismatches`` - a list of the positions within the - match_string where mismatches were found - - ``original`` - the original match_string used to compare - against the input string - - If ``mismatches`` is an empty list, then the match was an exact - match. - - Example:: - - patt = CloseMatch("ATCATCGAATGGA") - patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) - patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) - - # exact match - patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) - - # close match allowing up to 2 mismatches - patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2) - patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) - """ - def __init__(self, match_string, maxMismatches=1): - super(CloseMatch, self).__init__() - self.name = match_string - self.match_string = match_string - self.maxMismatches = maxMismatches - self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches) - self.mayIndexError = False - self.mayReturnEmpty = False - - def parseImpl(self, instring, loc, doActions=True): - start = loc - instrlen = len(instring) - maxloc = start + len(self.match_string) - - if maxloc <= instrlen: - match_string = self.match_string - match_stringloc = 0 - mismatches = [] - maxMismatches = self.maxMismatches - - for match_stringloc, s_m in enumerate(zip(instring[loc:maxloc], match_string)): - src, mat = s_m - if src != mat: - mismatches.append(match_stringloc) - if len(mismatches) > maxMismatches: - break - else: - loc = match_stringloc + 1 - results = ParseResults([instring[start:loc]]) - results['original'] = match_string - results['mismatches'] = mismatches - return loc, results - - raise ParseException(instring, loc, self.errmsg, self) - - -class Word(Token): - """Token for matching words composed of allowed character sets. - Defined with string containing all allowed initial characters, an - optional string containing allowed body characters (if omitted, - defaults to the initial character set), and an optional minimum, - maximum, and/or exact length. The default value for ``min`` is - 1 (a minimum value < 1 is not valid); the default values for - ``max`` and ``exact`` are 0, meaning no maximum or exact - length restriction. An optional ``excludeChars`` parameter can - list characters that might be found in the input ``bodyChars`` - string; useful to define a word of all printables except for one or - two characters, for instance. - - :class:`srange` is useful for defining custom character set strings - for defining ``Word`` expressions, using range notation from - regular expression character sets. - - A common mistake is to use :class:`Word` to match a specific literal - string, as in ``Word("Address")``. Remember that :class:`Word` - uses the string argument to define *sets* of matchable characters. - This expression would match "Add", "AAA", "dAred", or any other word - made up of the characters 'A', 'd', 'r', 'e', and 's'. To match an - exact literal string, use :class:`Literal` or :class:`Keyword`. - - pyparsing includes helper strings for building Words: - - - :class:`alphas` - - :class:`nums` - - :class:`alphanums` - - :class:`hexnums` - - :class:`alphas8bit` (alphabetic characters in ASCII range 128-255 - - accented, tilded, umlauted, etc.) - - :class:`punc8bit` (non-alphabetic characters in ASCII range - 128-255 - currency, symbols, superscripts, diacriticals, etc.) - - :class:`printables` (any non-whitespace character) - - Example:: - - # a word composed of digits - integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9")) - - # a word with a leading capital, and zero or more lowercase - capital_word = Word(alphas.upper(), alphas.lower()) - - # hostnames are alphanumeric, with leading alpha, and '-' - hostname = Word(alphas, alphanums + '-') - - # roman numeral (not a strict parser, accepts invalid mix of characters) - roman = Word("IVXLCDM") - - # any string of non-whitespace characters, except for ',' - csv_value = Word(printables, excludeChars=",") - """ - def __init__(self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None): - super(Word, self).__init__() - if excludeChars: - excludeChars = set(excludeChars) - initChars = ''.join(c for c in initChars if c not in excludeChars) - if bodyChars: - bodyChars = ''.join(c for c in bodyChars if c not in excludeChars) - self.initCharsOrig = initChars - self.initChars = set(initChars) - if bodyChars: - self.bodyCharsOrig = bodyChars - self.bodyChars = set(bodyChars) - else: - self.bodyCharsOrig = initChars - self.bodyChars = set(initChars) - - self.maxSpecified = max > 0 - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.asKeyword = asKeyword - - if ' ' not in self.initCharsOrig + self.bodyCharsOrig and (min == 1 and max == 0 and exact == 0): - if self.bodyCharsOrig == self.initCharsOrig: - self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) - elif len(self.initCharsOrig) == 1: - self.reString = "%s[%s]*" % (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - else: - self.reString = "[%s][%s]*" % (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - if self.asKeyword: - self.reString = r"\b" + self.reString + r"\b" - - try: - self.re = re.compile(self.reString) - except Exception: - self.re = None - else: - self.re_match = self.re.match - self.__class__ = _WordRegex - - def parseImpl(self, instring, loc, doActions=True): - if instring[loc] not in self.initChars: - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - instrlen = len(instring) - bodychars = self.bodyChars - maxloc = start + self.maxLen - maxloc = min(maxloc, instrlen) - while loc < maxloc and instring[loc] in bodychars: - loc += 1 - - throwException = False - if loc - start < self.minLen: - throwException = True - elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars: - throwException = True - elif self.asKeyword: - if (start > 0 and instring[start - 1] in bodychars - or loc < instrlen and instring[loc] in bodychars): - throwException = True - - if throwException: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__(self): - try: - return super(Word, self).__str__() - except Exception: - pass - - if self.strRepr is None: - - def charsAsStr(s): - if len(s) > 4: - return s[:4] + "..." - else: - return s - - if self.initCharsOrig != self.bodyCharsOrig: - self.strRepr = "W:(%s, %s)" % (charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig)) - else: - self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) - - return self.strRepr - -class _WordRegex(Word): - def parseImpl(self, instring, loc, doActions=True): - result = self.re_match(instring, loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - return loc, result.group() - - -class Char(_WordRegex): - """A short-cut class for defining ``Word(characters, exact=1)``, - when defining a match of any single character in a string of - characters. - """ - def __init__(self, charset, asKeyword=False, excludeChars=None): - super(Char, self).__init__(charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars) - self.reString = "[%s]" % _escapeRegexRangeChars(''.join(self.initChars)) - if asKeyword: - self.reString = r"\b%s\b" % self.reString - self.re = re.compile(self.reString) - self.re_match = self.re.match - - -class Regex(Token): - r"""Token for matching strings that match a given regular - expression. Defined with string specifying the regular expression in - a form recognized by the stdlib Python `re module `_. - If the given regex contains named groups (defined using ``(?P...)``), - these will be preserved as named parse results. - - If instead of the Python stdlib re module you wish to use a different RE module - (such as the `regex` module), you can replace it by either building your - Regex object with a compiled RE that was compiled using regex: - - Example:: - - realnum = Regex(r"[+-]?\d+\.\d*") - date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)') - # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression - roman = Regex(r"M{0,4}(CM|CD|D?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") - - # use regex module instead of stdlib re module to construct a Regex using - # a compiled regular expression - import regex - parser = pp.Regex(regex.compile(r'[0-9]')) - - """ - def __init__(self, pattern, flags=0, asGroupList=False, asMatch=False): - """The parameters ``pattern`` and ``flags`` are passed - to the ``re.compile()`` function as-is. See the Python - `re module `_ module for an - explanation of the acceptable patterns and flags. - """ - super(Regex, self).__init__() - - if isinstance(pattern, basestring): - if not pattern: - warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) - - self.pattern = pattern - self.flags = flags - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) - raise - - elif hasattr(pattern, 'pattern') and hasattr(pattern, 'match'): - self.re = pattern - self.pattern = self.reString = pattern.pattern - self.flags = flags - - else: - raise TypeError("Regex may only be constructed with a string or a compiled RE object") - - self.re_match = self.re.match - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = self.re_match("") is not None - self.asGroupList = asGroupList - self.asMatch = asMatch - if self.asGroupList: - self.parseImpl = self.parseImplAsGroupList - if self.asMatch: - self.parseImpl = self.parseImplAsMatch - - def parseImpl(self, instring, loc, doActions=True): - result = self.re_match(instring, loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = ParseResults(result.group()) - d = result.groupdict() - if d: - for k, v in d.items(): - ret[k] = v - return loc, ret - - def parseImplAsGroupList(self, instring, loc, doActions=True): - result = self.re_match(instring, loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = result.groups() - return loc, ret - - def parseImplAsMatch(self, instring, loc, doActions=True): - result = self.re_match(instring, loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = result - return loc, ret - - def __str__(self): - try: - return super(Regex, self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "Re:(%s)" % repr(self.pattern) - - return self.strRepr - - def sub(self, repl): - r""" - Return Regex with an attached parse action to transform the parsed - result as if called using `re.sub(expr, repl, string) `_. - - Example:: - - make_html = Regex(r"(\w+):(.*?):").sub(r"<\1>\2") - print(make_html.transformString("h1:main title:")) - # prints "

main title

" - """ - if self.asGroupList: - warnings.warn("cannot use sub() with Regex(asGroupList=True)", - SyntaxWarning, stacklevel=2) - raise SyntaxError() - - if self.asMatch and callable(repl): - warnings.warn("cannot use sub() with a callable with Regex(asMatch=True)", - SyntaxWarning, stacklevel=2) - raise SyntaxError() - - if self.asMatch: - def pa(tokens): - return tokens[0].expand(repl) - else: - def pa(tokens): - return self.re.sub(repl, tokens[0]) - return self.addParseAction(pa) - -class QuotedString(Token): - r""" - Token for matching strings that are delimited by quoting characters. - - Defined with the following parameters: - - - quoteChar - string of one or more characters defining the - quote delimiting string - - escChar - character to escape quotes, typically backslash - (default= ``None``) - - escQuote - special quote sequence to escape an embedded quote - string (such as SQL's ``""`` to escape an embedded ``"``) - (default= ``None``) - - multiline - boolean indicating whether quotes can span - multiple lines (default= ``False``) - - unquoteResults - boolean indicating whether the matched text - should be unquoted (default= ``True``) - - endQuoteChar - string of one or more characters defining the - end of the quote delimited string (default= ``None`` => same as - quoteChar) - - convertWhitespaceEscapes - convert escaped whitespace - (``'\t'``, ``'\n'``, etc.) to actual whitespace - (default= ``True``) - - Example:: - - qs = QuotedString('"') - print(qs.searchString('lsjdf "This is the quote" sldjf')) - complex_qs = QuotedString('{{', endQuoteChar='}}') - print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf')) - sql_qs = QuotedString('"', escQuote='""') - print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf')) - - prints:: - - [['This is the quote']] - [['This is the "quote"']] - [['This is the quote with "embedded" quotes']] - """ - def __init__(self, quoteChar, escChar=None, escQuote=None, multiline=False, - unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): - super(QuotedString, self).__init__() - - # remove white space from quote chars - wont work anyway - quoteChar = quoteChar.strip() - if not quoteChar: - warnings.warn("quoteChar cannot be the empty string", SyntaxWarning, stacklevel=2) - raise SyntaxError() - - if endQuoteChar is None: - endQuoteChar = quoteChar - else: - endQuoteChar = endQuoteChar.strip() - if not endQuoteChar: - warnings.warn("endQuoteChar cannot be the empty string", SyntaxWarning, stacklevel=2) - raise SyntaxError() - - self.quoteChar = quoteChar - self.quoteCharLen = len(quoteChar) - self.firstQuoteChar = quoteChar[0] - self.endQuoteChar = endQuoteChar - self.endQuoteCharLen = len(endQuoteChar) - self.escChar = escChar - self.escQuote = escQuote - self.unquoteResults = unquoteResults - self.convertWhitespaceEscapes = convertWhitespaceEscapes - - if multiline: - self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % (re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '')) - else: - self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % (re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '')) - if len(self.endQuoteChar) > 1: - self.pattern += ( - '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar) - 1, 0, -1)) + ')') - - if escQuote: - self.pattern += (r'|(?:%s)' % re.escape(escQuote)) - if escChar: - self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - self.escCharReplacePattern = re.escape(self.escChar) + "(.)" - self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - self.re_match = self.re.match - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) - raise - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl(self, instring, loc, doActions=True): - result = instring[loc] == self.firstQuoteChar and self.re_match(instring, loc) or None - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = result.group() - - if self.unquoteResults: - - # strip off quotes - ret = ret[self.quoteCharLen: -self.endQuoteCharLen] - - if isinstance(ret, basestring): - # replace escaped whitespace - if '\\' in ret and self.convertWhitespaceEscapes: - ws_map = { - r'\t': '\t', - r'\n': '\n', - r'\f': '\f', - r'\r': '\r', - } - for wslit, wschar in ws_map.items(): - ret = ret.replace(wslit, wschar) - - # replace escaped characters - if self.escChar: - ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) - - # replace escaped quotes - if self.escQuote: - ret = ret.replace(self.escQuote, self.endQuoteChar) - - return loc, ret - - def __str__(self): - try: - return super(QuotedString, self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) - - return self.strRepr - - -class CharsNotIn(Token): - """Token for matching words composed of characters *not* in a given - set (will include whitespace in matched characters if not listed in - the provided exclusion set - see example). Defined with string - containing all disallowed characters, and an optional minimum, - maximum, and/or exact length. The default value for ``min`` is - 1 (a minimum value < 1 is not valid); the default values for - ``max`` and ``exact`` are 0, meaning no maximum or exact - length restriction. - - Example:: - - # define a comma-separated-value as anything that is not a ',' - csv_value = CharsNotIn(',') - print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213")) - - prints:: - - ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] - """ - def __init__(self, notChars, min=1, max=0, exact=0): - super(CharsNotIn, self).__init__() - self.skipWhitespace = False - self.notChars = notChars - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use " - "Optional(CharsNotIn()) if zero-length char group is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = (self.minLen == 0) - self.mayIndexError = False - - def parseImpl(self, instring, loc, doActions=True): - if instring[loc] in self.notChars: - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - notchars = self.notChars - maxlen = min(start + self.maxLen, len(instring)) - while loc < maxlen and instring[loc] not in notchars: - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__(self): - try: - return super(CharsNotIn, self).__str__() - except Exception: - pass - - if self.strRepr is None: - if len(self.notChars) > 4: - self.strRepr = "!W:(%s...)" % self.notChars[:4] - else: - self.strRepr = "!W:(%s)" % self.notChars - - return self.strRepr - -class White(Token): - """Special matching class for matching whitespace. Normally, - whitespace is ignored by pyparsing grammars. This class is included - when some whitespace structures are significant. Define with - a string containing the whitespace characters to be matched; default - is ``" \\t\\r\\n"``. Also takes optional ``min``, - ``max``, and ``exact`` arguments, as defined for the - :class:`Word` class. - """ - whiteStrs = { - ' ' : '', - '\t': '', - '\n': '', - '\r': '', - '\f': '', - u'\u00A0': '', - u'\u1680': '', - u'\u180E': '', - u'\u2000': '', - u'\u2001': '', - u'\u2002': '', - u'\u2003': '', - u'\u2004': '', - u'\u2005': '', - u'\u2006': '', - u'\u2007': '', - u'\u2008': '', - u'\u2009': '', - u'\u200A': '', - u'\u200B': '', - u'\u202F': '', - u'\u205F': '', - u'\u3000': '', - } - def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White, self).__init__() - self.matchWhite = ws - self.setWhitespaceChars("".join(c for c in self.whiteChars if c not in self.matchWhite)) - # ~ self.leaveWhitespace() - self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite)) - self.mayReturnEmpty = True - self.errmsg = "Expected " + self.name - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - def parseImpl(self, instring, loc, doActions=True): - if instring[loc] not in self.matchWhite: - raise ParseException(instring, loc, self.errmsg, self) - start = loc - loc += 1 - maxloc = start + self.maxLen - maxloc = min(maxloc, len(instring)) - while loc < maxloc and instring[loc] in self.matchWhite: - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - -class _PositionToken(Token): - def __init__(self): - super(_PositionToken, self).__init__() - self.name = self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - -class GoToColumn(_PositionToken): - """Token to advance to a specific column of input text; useful for - tabular report scraping. - """ - def __init__(self, colno): - super(GoToColumn, self).__init__() - self.col = colno - - def preParse(self, instring, loc): - if col(loc, instring) != self.col: - instrlen = len(instring) - if self.ignoreExprs: - loc = self._skipIgnorables(instring, loc) - while loc < instrlen and instring[loc].isspace() and col(loc, instring) != self.col: - loc += 1 - return loc - - def parseImpl(self, instring, loc, doActions=True): - thiscol = col(loc, instring) - if thiscol > self.col: - raise ParseException(instring, loc, "Text not in expected column", self) - newloc = loc + self.col - thiscol - ret = instring[loc: newloc] - return newloc, ret - - -class LineStart(_PositionToken): - r"""Matches if current position is at the beginning of a line within - the parse string - - Example:: - - test = '''\ - AAA this line - AAA and this line - AAA but not this one - B AAA and definitely not this one - ''' - - for t in (LineStart() + 'AAA' + restOfLine).searchString(test): - print(t) - - prints:: - - ['AAA', ' this line'] - ['AAA', ' and this line'] - - """ - def __init__(self): - super(LineStart, self).__init__() - self.errmsg = "Expected start of line" - - def parseImpl(self, instring, loc, doActions=True): - if col(loc, instring) == 1: - return loc, [] - raise ParseException(instring, loc, self.errmsg, self) - -class LineEnd(_PositionToken): - """Matches if current position is at the end of a line within the - parse string - """ - def __init__(self): - super(LineEnd, self).__init__() - self.setWhitespaceChars(ParserElement.DEFAULT_WHITE_CHARS.replace("\n", "")) - self.errmsg = "Expected end of line" - - def parseImpl(self, instring, loc, doActions=True): - if loc < len(instring): - if instring[loc] == "\n": - return loc + 1, "\n" - else: - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc + 1, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class StringStart(_PositionToken): - """Matches if current position is at the beginning of the parse - string - """ - def __init__(self): - super(StringStart, self).__init__() - self.errmsg = "Expected start of text" - - def parseImpl(self, instring, loc, doActions=True): - if loc != 0: - # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse(instring, 0): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class StringEnd(_PositionToken): - """Matches if current position is at the end of the parse string - """ - def __init__(self): - super(StringEnd, self).__init__() - self.errmsg = "Expected end of text" - - def parseImpl(self, instring, loc, doActions=True): - if loc < len(instring): - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc + 1, [] - elif loc > len(instring): - return loc, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class WordStart(_PositionToken): - """Matches if the current position is at the beginning of a Word, - and is not preceded by any character in a given set of - ``wordChars`` (default= ``printables``). To emulate the - ``\b`` behavior of regular expressions, use - ``WordStart(alphanums)``. ``WordStart`` will also match at - the beginning of the string being parsed, or at the beginning of - a line. - """ - def __init__(self, wordChars=printables): - super(WordStart, self).__init__() - self.wordChars = set(wordChars) - self.errmsg = "Not at the start of a word" - - def parseImpl(self, instring, loc, doActions=True): - if loc != 0: - if (instring[loc - 1] in self.wordChars - or instring[loc] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class WordEnd(_PositionToken): - """Matches if the current position is at the end of a Word, and is - not followed by any character in a given set of ``wordChars`` - (default= ``printables``). To emulate the ``\b`` behavior of - regular expressions, use ``WordEnd(alphanums)``. ``WordEnd`` - will also match at the end of the string being parsed, or at the end - of a line. - """ - def __init__(self, wordChars=printables): - super(WordEnd, self).__init__() - self.wordChars = set(wordChars) - self.skipWhitespace = False - self.errmsg = "Not at the end of a word" - - def parseImpl(self, instring, loc, doActions=True): - instrlen = len(instring) - if instrlen > 0 and loc < instrlen: - if (instring[loc] in self.wordChars or - instring[loc - 1] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - -class ParseExpression(ParserElement): - """Abstract subclass of ParserElement, for combining and - post-processing parsed tokens. - """ - def __init__(self, exprs, savelist=False): - super(ParseExpression, self).__init__(savelist) - if isinstance(exprs, _generatorType): - exprs = list(exprs) - - if isinstance(exprs, basestring): - self.exprs = [self._literalStringClass(exprs)] - elif isinstance(exprs, ParserElement): - self.exprs = [exprs] - elif isinstance(exprs, Iterable): - exprs = list(exprs) - # if sequence of strings provided, wrap with Literal - if any(isinstance(expr, basestring) for expr in exprs): - exprs = (self._literalStringClass(e) if isinstance(e, basestring) else e for e in exprs) - self.exprs = list(exprs) - else: - try: - self.exprs = list(exprs) - except TypeError: - self.exprs = [exprs] - self.callPreparse = False - - def append(self, other): - self.exprs.append(other) - self.strRepr = None - return self - - def leaveWhitespace(self): - """Extends ``leaveWhitespace`` defined in base class, and also invokes ``leaveWhitespace`` on - all contained expressions.""" - self.skipWhitespace = False - self.exprs = [e.copy() for e in self.exprs] - for e in self.exprs: - e.leaveWhitespace() - return self - - def ignore(self, other): - if isinstance(other, Suppress): - if other not in self.ignoreExprs: - super(ParseExpression, self).ignore(other) - for e in self.exprs: - e.ignore(self.ignoreExprs[-1]) - else: - super(ParseExpression, self).ignore(other) - for e in self.exprs: - e.ignore(self.ignoreExprs[-1]) - return self - - def __str__(self): - try: - return super(ParseExpression, self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.exprs)) - return self.strRepr - - def streamline(self): - super(ParseExpression, self).streamline() - - for e in self.exprs: - e.streamline() - - # collapse nested And's of the form And(And(And(a, b), c), d) to And(a, b, c, d) - # but only if there are no parse actions or resultsNames on the nested And's - # (likewise for Or's and MatchFirst's) - if len(self.exprs) == 2: - other = self.exprs[0] - if (isinstance(other, self.__class__) - and not other.parseAction - and other.resultsName is None - and not other.debug): - self.exprs = other.exprs[:] + [self.exprs[1]] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - other = self.exprs[-1] - if (isinstance(other, self.__class__) - and not other.parseAction - and other.resultsName is None - and not other.debug): - self.exprs = self.exprs[:-1] + other.exprs[:] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - self.errmsg = "Expected " + _ustr(self) - - return self - - def validate(self, validateTrace=None): - tmp = (validateTrace if validateTrace is not None else [])[:] + [self] - for e in self.exprs: - e.validate(tmp) - self.checkRecursion([]) - - def copy(self): - ret = super(ParseExpression, self).copy() - ret.exprs = [e.copy() for e in self.exprs] - return ret - - def _setResultsName(self, name, listAllMatches=False): - if __diag__.warn_ungrouped_named_tokens_in_collection: - for e in self.exprs: - if isinstance(e, ParserElement) and e.resultsName: - warnings.warn("{0}: setting results name {1!r} on {2} expression " - "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection", - name, - type(self).__name__, - e.resultsName), - stacklevel=3) - - return super(ParseExpression, self)._setResultsName(name, listAllMatches) - - -class And(ParseExpression): - """ - Requires all given :class:`ParseExpression` s to be found in the given order. - Expressions may be separated by whitespace. - May be constructed using the ``'+'`` operator. - May also be constructed using the ``'-'`` operator, which will - suppress backtracking. - - Example:: - - integer = Word(nums) - name_expr = OneOrMore(Word(alphas)) - - expr = And([integer("id"), name_expr("name"), integer("age")]) - # more easily written as: - expr = integer("id") + name_expr("name") + integer("age") - """ - - class _ErrorStop(Empty): - def __init__(self, *args, **kwargs): - super(And._ErrorStop, self).__init__(*args, **kwargs) - self.name = '-' - self.leaveWhitespace() - - def __init__(self, exprs, savelist=True): - exprs = list(exprs) - if exprs and Ellipsis in exprs: - tmp = [] - for i, expr in enumerate(exprs): - if expr is Ellipsis: - if i < len(exprs) - 1: - skipto_arg = (Empty() + exprs[i + 1]).exprs[-1] - tmp.append(SkipTo(skipto_arg)("_skipped*")) - else: - raise Exception("cannot construct And with sequence ending in ...") - else: - tmp.append(expr) - exprs[:] = tmp - super(And, self).__init__(exprs, savelist) - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.setWhitespaceChars(self.exprs[0].whiteChars) - self.skipWhitespace = self.exprs[0].skipWhitespace - self.callPreparse = True - - def streamline(self): - # collapse any _PendingSkip's - if self.exprs: - if any(isinstance(e, ParseExpression) and e.exprs and isinstance(e.exprs[-1], _PendingSkip) - for e in self.exprs[:-1]): - for i, e in enumerate(self.exprs[:-1]): - if e is None: - continue - if (isinstance(e, ParseExpression) - and e.exprs and isinstance(e.exprs[-1], _PendingSkip)): - e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1] - self.exprs[i + 1] = None - self.exprs = [e for e in self.exprs if e is not None] - - super(And, self).streamline() - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - return self - - def parseImpl(self, instring, loc, doActions=True): - # pass False as last arg to _parse for first element, since we already - # pre-parsed the string as part of our And pre-parsing - loc, resultlist = self.exprs[0]._parse(instring, loc, doActions, callPreParse=False) - errorStop = False - for e in self.exprs[1:]: - if isinstance(e, And._ErrorStop): - errorStop = True - continue - if errorStop: - try: - loc, exprtokens = e._parse(instring, loc, doActions) - except ParseSyntaxException: - raise - except ParseBaseException as pe: - pe.__traceback__ = None - raise ParseSyntaxException._from_exception(pe) - except IndexError: - raise ParseSyntaxException(instring, len(instring), self.errmsg, self) - else: - loc, exprtokens = e._parse(instring, loc, doActions) - if exprtokens or exprtokens.haskeys(): - resultlist += exprtokens - return loc, resultlist - - def __iadd__(self, other): - if isinstance(other, basestring): - other = self._literalStringClass(other) - return self.append(other) # And([self, other]) - - def checkRecursion(self, parseElementList): - subRecCheckList = parseElementList[:] + [self] - for e in self.exprs: - e.checkRecursion(subRecCheckList) - if not e.mayReturnEmpty: - break - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - -class Or(ParseExpression): - """Requires that at least one :class:`ParseExpression` is found. If - two expressions match, the expression that matches the longest - string will be used. May be constructed using the ``'^'`` - operator. - - Example:: - - # construct Or using '^' operator - - number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) - print(number.searchString("123 3.1416 789")) - - prints:: - - [['123'], ['3.1416'], ['789']] - """ - def __init__(self, exprs, savelist=False): - super(Or, self).__init__(exprs, savelist) - if self.exprs: - self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - else: - self.mayReturnEmpty = True - - def streamline(self): - super(Or, self).streamline() - if __compat__.collect_all_And_tokens: - self.saveAsList = any(e.saveAsList for e in self.exprs) - return self - - def parseImpl(self, instring, loc, doActions=True): - maxExcLoc = -1 - maxException = None - matches = [] - for e in self.exprs: - try: - loc2 = e.tryParse(instring, loc) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring, len(instring), e.errmsg, self) - maxExcLoc = len(instring) - else: - # save match among all matches, to retry longest to shortest - matches.append((loc2, e)) - - if matches: - # re-evaluate all matches in descending order of length of match, in case attached actions - # might change whether or how much they match of the input. - matches.sort(key=itemgetter(0), reverse=True) - - if not doActions: - # no further conditions or parse actions to change the selection of - # alternative, so the first match will be the best match - best_expr = matches[0][1] - return best_expr._parse(instring, loc, doActions) - - longest = -1, None - for loc1, expr1 in matches: - if loc1 <= longest[0]: - # already have a longer match than this one will deliver, we are done - return longest - - try: - loc2, toks = expr1._parse(instring, loc, doActions) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - else: - if loc2 >= loc1: - return loc2, toks - # didn't match as much as before - elif loc2 > longest[0]: - longest = loc2, toks - - if longest != (-1, None): - return longest - - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - - def __ixor__(self, other): - if isinstance(other, basestring): - other = self._literalStringClass(other) - return self.append(other) # Or([self, other]) - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion(self, parseElementList): - subRecCheckList = parseElementList[:] + [self] - for e in self.exprs: - e.checkRecursion(subRecCheckList) - - def _setResultsName(self, name, listAllMatches=False): - if (not __compat__.collect_all_And_tokens - and __diag__.warn_multiple_tokens_in_named_alternation): - if any(isinstance(e, And) for e in self.exprs): - warnings.warn("{0}: setting results name {1!r} on {2} expression " - "may only return a single token for an And alternative, " - "in future will return the full list of tokens".format( - "warn_multiple_tokens_in_named_alternation", name, type(self).__name__), - stacklevel=3) - - return super(Or, self)._setResultsName(name, listAllMatches) - - -class MatchFirst(ParseExpression): - """Requires that at least one :class:`ParseExpression` is found. If - two expressions match, the first one listed is the one that will - match. May be constructed using the ``'|'`` operator. - - Example:: - - # construct MatchFirst using '|' operator - - # watch the order of expressions to match - number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) - print(number.searchString("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] - - # put more selective expression first - number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) - print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] - """ - def __init__(self, exprs, savelist=False): - super(MatchFirst, self).__init__(exprs, savelist) - if self.exprs: - self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - else: - self.mayReturnEmpty = True - - def streamline(self): - super(MatchFirst, self).streamline() - if __compat__.collect_all_And_tokens: - self.saveAsList = any(e.saveAsList for e in self.exprs) - return self - - def parseImpl(self, instring, loc, doActions=True): - maxExcLoc = -1 - maxException = None - for e in self.exprs: - try: - ret = e._parse(instring, loc, doActions) - return ret - except ParseException as err: - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring, len(instring), e.errmsg, self) - maxExcLoc = len(instring) - - # only got here if no expression matched, raise exception for match that made it the furthest - else: - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - def __ior__(self, other): - if isinstance(other, basestring): - other = self._literalStringClass(other) - return self.append(other) # MatchFirst([self, other]) - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion(self, parseElementList): - subRecCheckList = parseElementList[:] + [self] - for e in self.exprs: - e.checkRecursion(subRecCheckList) - - def _setResultsName(self, name, listAllMatches=False): - if (not __compat__.collect_all_And_tokens - and __diag__.warn_multiple_tokens_in_named_alternation): - if any(isinstance(e, And) for e in self.exprs): - warnings.warn("{0}: setting results name {1!r} on {2} expression " - "may only return a single token for an And alternative, " - "in future will return the full list of tokens".format( - "warn_multiple_tokens_in_named_alternation", name, type(self).__name__), - stacklevel=3) - - return super(MatchFirst, self)._setResultsName(name, listAllMatches) - - -class Each(ParseExpression): - """Requires all given :class:`ParseExpression` s to be found, but in - any order. Expressions may be separated by whitespace. - - May be constructed using the ``'&'`` operator. - - Example:: - - color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") - shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON") - integer = Word(nums) - shape_attr = "shape:" + shape_type("shape") - posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn") - color_attr = "color:" + color("color") - size_attr = "size:" + integer("size") - - # use Each (using operator '&') to accept attributes in any order - # (shape and posn are required, color and size are optional) - shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr) - - shape_spec.runTests(''' - shape: SQUARE color: BLACK posn: 100, 120 - shape: CIRCLE size: 50 color: BLUE posn: 50,80 - color:GREEN size:20 shape:TRIANGLE posn:20,40 - ''' - ) - - prints:: - - shape: SQUARE color: BLACK posn: 100, 120 - ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']] - - color: BLACK - - posn: ['100', ',', '120'] - - x: 100 - - y: 120 - - shape: SQUARE - - - shape: CIRCLE size: 50 color: BLUE posn: 50,80 - ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']] - - color: BLUE - - posn: ['50', ',', '80'] - - x: 50 - - y: 80 - - shape: CIRCLE - - size: 50 - - - color: GREEN size: 20 shape: TRIANGLE posn: 20,40 - ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']] - - color: GREEN - - posn: ['20', ',', '40'] - - x: 20 - - y: 40 - - shape: TRIANGLE - - size: 20 - """ - def __init__(self, exprs, savelist=True): - super(Each, self).__init__(exprs, savelist) - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.skipWhitespace = True - self.initExprGroups = True - self.saveAsList = True - - def streamline(self): - super(Each, self).streamline() - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - return self - - def parseImpl(self, instring, loc, doActions=True): - if self.initExprGroups: - self.opt1map = dict((id(e.expr), e) for e in self.exprs if isinstance(e, Optional)) - opt1 = [e.expr for e in self.exprs if isinstance(e, Optional)] - opt2 = [e for e in self.exprs if e.mayReturnEmpty and not isinstance(e, (Optional, Regex))] - self.optionals = opt1 + opt2 - self.multioptionals = [e.expr for e in self.exprs if isinstance(e, ZeroOrMore)] - self.multirequired = [e.expr for e in self.exprs if isinstance(e, OneOrMore)] - self.required = [e for e in self.exprs if not isinstance(e, (Optional, ZeroOrMore, OneOrMore))] - self.required += self.multirequired - self.initExprGroups = False - tmpLoc = loc - tmpReqd = self.required[:] - tmpOpt = self.optionals[:] - matchOrder = [] - - keepMatching = True - while keepMatching: - tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] - for e in tmpExprs: - try: - tmpLoc = e.tryParse(instring, tmpLoc) - except ParseException: - failed.append(e) - else: - matchOrder.append(self.opt1map.get(id(e), e)) - if e in tmpReqd: - tmpReqd.remove(e) - elif e in tmpOpt: - tmpOpt.remove(e) - if len(failed) == len(tmpExprs): - keepMatching = False - - if tmpReqd: - missing = ", ".join(_ustr(e) for e in tmpReqd) - raise ParseException(instring, loc, "Missing one or more required elements (%s)" % missing) - - # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e, Optional) and e.expr in tmpOpt] - - resultlist = [] - for e in matchOrder: - loc, results = e._parse(instring, loc, doActions) - resultlist.append(results) - - finalResults = sum(resultlist, ParseResults([])) - return loc, finalResults - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion(self, parseElementList): - subRecCheckList = parseElementList[:] + [self] - for e in self.exprs: - e.checkRecursion(subRecCheckList) - - -class ParseElementEnhance(ParserElement): - """Abstract subclass of :class:`ParserElement`, for combining and - post-processing parsed tokens. - """ - def __init__(self, expr, savelist=False): - super(ParseElementEnhance, self).__init__(savelist) - if isinstance(expr, basestring): - if issubclass(self._literalStringClass, Token): - expr = self._literalStringClass(expr) - else: - expr = self._literalStringClass(Literal(expr)) - self.expr = expr - self.strRepr = None - if expr is not None: - self.mayIndexError = expr.mayIndexError - self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars(expr.whiteChars) - self.skipWhitespace = expr.skipWhitespace - self.saveAsList = expr.saveAsList - self.callPreparse = expr.callPreparse - self.ignoreExprs.extend(expr.ignoreExprs) - - def parseImpl(self, instring, loc, doActions=True): - if self.expr is not None: - return self.expr._parse(instring, loc, doActions, callPreParse=False) - else: - raise ParseException("", loc, self.errmsg, self) - - def leaveWhitespace(self): - self.skipWhitespace = False - self.expr = self.expr.copy() - if self.expr is not None: - self.expr.leaveWhitespace() - return self - - def ignore(self, other): - if isinstance(other, Suppress): - if other not in self.ignoreExprs: - super(ParseElementEnhance, self).ignore(other) - if self.expr is not None: - self.expr.ignore(self.ignoreExprs[-1]) - else: - super(ParseElementEnhance, self).ignore(other) - if self.expr is not None: - self.expr.ignore(self.ignoreExprs[-1]) - return self - - def streamline(self): - super(ParseElementEnhance, self).streamline() - if self.expr is not None: - self.expr.streamline() - return self - - def checkRecursion(self, parseElementList): - if self in parseElementList: - raise RecursiveGrammarException(parseElementList + [self]) - subRecCheckList = parseElementList[:] + [self] - if self.expr is not None: - self.expr.checkRecursion(subRecCheckList) - - def validate(self, validateTrace=None): - if validateTrace is None: - validateTrace = [] - tmp = validateTrace[:] + [self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__(self): - try: - return super(ParseElementEnhance, self).__str__() - except Exception: - pass - - if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % (self.__class__.__name__, _ustr(self.expr)) - return self.strRepr - - -class FollowedBy(ParseElementEnhance): - """Lookahead matching of the given parse expression. - ``FollowedBy`` does *not* advance the parsing position within - the input string, it only verifies that the specified parse - expression matches at the current position. ``FollowedBy`` - always returns a null token list. If any results names are defined - in the lookahead expression, those *will* be returned for access by - name. - - Example:: - - # use FollowedBy to match a label only if it is followed by a ':' - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - - OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint() - - prints:: - - [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] - """ - def __init__(self, expr): - super(FollowedBy, self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl(self, instring, loc, doActions=True): - # by using self._expr.parse and deleting the contents of the returned ParseResults list - # we keep any named results that were defined in the FollowedBy expression - _, ret = self.expr._parse(instring, loc, doActions=doActions) - del ret[:] - - return loc, ret - - -class PrecededBy(ParseElementEnhance): - """Lookbehind matching of the given parse expression. - ``PrecededBy`` does not advance the parsing position within the - input string, it only verifies that the specified parse expression - matches prior to the current position. ``PrecededBy`` always - returns a null token list, but if a results name is defined on the - given expression, it is returned. - - Parameters: - - - expr - expression that must match prior to the current parse - location - - retreat - (default= ``None``) - (int) maximum number of characters - to lookbehind prior to the current parse location - - If the lookbehind expression is a string, Literal, Keyword, or - a Word or CharsNotIn with a specified exact or maximum length, then - the retreat parameter is not required. Otherwise, retreat must be - specified to give a maximum number of characters to look back from - the current parse position for a lookbehind match. - - Example:: - - # VB-style variable names with type prefixes - int_var = PrecededBy("#") + pyparsing_common.identifier - str_var = PrecededBy("$") + pyparsing_common.identifier - - """ - def __init__(self, expr, retreat=None): - super(PrecededBy, self).__init__(expr) - self.expr = self.expr().leaveWhitespace() - self.mayReturnEmpty = True - self.mayIndexError = False - self.exact = False - if isinstance(expr, str): - retreat = len(expr) - self.exact = True - elif isinstance(expr, (Literal, Keyword)): - retreat = expr.matchLen - self.exact = True - elif isinstance(expr, (Word, CharsNotIn)) and expr.maxLen != _MAX_INT: - retreat = expr.maxLen - self.exact = True - elif isinstance(expr, _PositionToken): - retreat = 0 - self.exact = True - self.retreat = retreat - self.errmsg = "not preceded by " + str(expr) - self.skipWhitespace = False - self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None))) - - def parseImpl(self, instring, loc=0, doActions=True): - if self.exact: - if loc < self.retreat: - raise ParseException(instring, loc, self.errmsg) - start = loc - self.retreat - _, ret = self.expr._parse(instring, start) - else: - # retreat specified a maximum lookbehind window, iterate - test_expr = self.expr + StringEnd() - instring_slice = instring[max(0, loc - self.retreat):loc] - last_expr = ParseException(instring, loc, self.errmsg) - for offset in range(1, min(loc, self.retreat + 1)+1): - try: - # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:])) - _, ret = test_expr._parse(instring_slice, len(instring_slice) - offset) - except ParseBaseException as pbe: - last_expr = pbe - else: - break - else: - raise last_expr - return loc, ret - - -class NotAny(ParseElementEnhance): - """Lookahead to disallow matching with the given parse expression. - ``NotAny`` does *not* advance the parsing position within the - input string, it only verifies that the specified parse expression - does *not* match at the current position. Also, ``NotAny`` does - *not* skip over leading whitespace. ``NotAny`` always returns - a null token list. May be constructed using the '~' operator. - - Example:: - - AND, OR, NOT = map(CaselessKeyword, "AND OR NOT".split()) - - # take care not to mistake keywords for identifiers - ident = ~(AND | OR | NOT) + Word(alphas) - boolean_term = Optional(NOT) + ident - - # very crude boolean expression - to support parenthesis groups and - # operation hierarchy, use infixNotation - boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term) - - # integers that are followed by "." are actually floats - integer = Word(nums) + ~Char(".") - """ - def __init__(self, expr): - super(NotAny, self).__init__(expr) - # ~ self.leaveWhitespace() - self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs - self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, " + _ustr(self.expr) - - def parseImpl(self, instring, loc, doActions=True): - if self.expr.canParseNext(instring, loc): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "~{" + _ustr(self.expr) + "}" - - return self.strRepr - -class _MultipleMatch(ParseElementEnhance): - def __init__(self, expr, stopOn=None): - super(_MultipleMatch, self).__init__(expr) - self.saveAsList = True - ender = stopOn - if isinstance(ender, basestring): - ender = self._literalStringClass(ender) - self.stopOn(ender) - - def stopOn(self, ender): - if isinstance(ender, basestring): - ender = self._literalStringClass(ender) - self.not_ender = ~ender if ender is not None else None - return self - - def parseImpl(self, instring, loc, doActions=True): - self_expr_parse = self.expr._parse - self_skip_ignorables = self._skipIgnorables - check_ender = self.not_ender is not None - if check_ender: - try_not_ender = self.not_ender.tryParse - - # must be at least one (but first see if we are the stopOn sentinel; - # if so, fail) - if check_ender: - try_not_ender(instring, loc) - loc, tokens = self_expr_parse(instring, loc, doActions, callPreParse=False) - try: - hasIgnoreExprs = (not not self.ignoreExprs) - while 1: - if check_ender: - try_not_ender(instring, loc) - if hasIgnoreExprs: - preloc = self_skip_ignorables(instring, loc) - else: - preloc = loc - loc, tmptokens = self_expr_parse(instring, preloc, doActions) - if tmptokens or tmptokens.haskeys(): - tokens += tmptokens - except (ParseException, IndexError): - pass - - return loc, tokens - - def _setResultsName(self, name, listAllMatches=False): - if __diag__.warn_ungrouped_named_tokens_in_collection: - for e in [self.expr] + getattr(self.expr, 'exprs', []): - if isinstance(e, ParserElement) and e.resultsName: - warnings.warn("{0}: setting results name {1!r} on {2} expression " - "collides with {3!r} on contained expression".format("warn_ungrouped_named_tokens_in_collection", - name, - type(self).__name__, - e.resultsName), - stacklevel=3) - - return super(_MultipleMatch, self)._setResultsName(name, listAllMatches) - - -class OneOrMore(_MultipleMatch): - """Repetition of one or more of the given expression. - - Parameters: - - expr - expression that must match one or more times - - stopOn - (default= ``None``) - expression for a terminating sentinel - (only required if the sentinel would ordinarily match the repetition - expression) - - Example:: - - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) - - text = "shape: SQUARE posn: upper left color: BLACK" - OneOrMore(attr_expr).parseString(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] - - # use stopOn attribute for OneOrMore to avoid reading label string as part of the data - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] - - # could also be written as - (attr_expr * (1,)).parseString(text).pprint() - """ - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + _ustr(self.expr) + "}..." - - return self.strRepr - -class ZeroOrMore(_MultipleMatch): - """Optional repetition of zero or more of the given expression. - - Parameters: - - expr - expression that must match zero or more times - - stopOn - (default= ``None``) - expression for a terminating sentinel - (only required if the sentinel would ordinarily match the repetition - expression) - - Example: similar to :class:`OneOrMore` - """ - def __init__(self, expr, stopOn=None): - super(ZeroOrMore, self).__init__(expr, stopOn=stopOn) - self.mayReturnEmpty = True - - def parseImpl(self, instring, loc, doActions=True): - try: - return super(ZeroOrMore, self).parseImpl(instring, loc, doActions) - except (ParseException, IndexError): - return loc, [] - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]..." - - return self.strRepr - - -class _NullToken(object): - def __bool__(self): - return False - __nonzero__ = __bool__ - def __str__(self): - return "" - -class Optional(ParseElementEnhance): - """Optional matching of the given expression. - - Parameters: - - expr - expression that must match zero or more times - - default (optional) - value to be returned if the optional expression is not found. - - Example:: - - # US postal code can be a 5-digit zip, plus optional 4-digit qualifier - zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4))) - zip.runTests(''' - # traditional ZIP code - 12345 - - # ZIP+4 form - 12101-0001 - - # invalid ZIP - 98765- - ''') - - prints:: - - # traditional ZIP code - 12345 - ['12345'] - - # ZIP+4 form - 12101-0001 - ['12101-0001'] - - # invalid ZIP - 98765- - ^ - FAIL: Expected end of text (at char 5), (line:1, col:6) - """ - __optionalNotMatched = _NullToken() - - def __init__(self, expr, default=__optionalNotMatched): - super(Optional, self).__init__(expr, savelist=False) - self.saveAsList = self.expr.saveAsList - self.defaultValue = default - self.mayReturnEmpty = True - - def parseImpl(self, instring, loc, doActions=True): - try: - loc, tokens = self.expr._parse(instring, loc, doActions, callPreParse=False) - except (ParseException, IndexError): - if self.defaultValue is not self.__optionalNotMatched: - if self.expr.resultsName: - tokens = ParseResults([self.defaultValue]) - tokens[self.expr.resultsName] = self.defaultValue - else: - tokens = [self.defaultValue] - else: - tokens = [] - return loc, tokens - - def __str__(self): - if hasattr(self, "name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]" - - return self.strRepr - -class SkipTo(ParseElementEnhance): - """Token for skipping over all undefined text until the matched - expression is found. - - Parameters: - - expr - target expression marking the end of the data to be skipped - - include - (default= ``False``) if True, the target expression is also parsed - (the skipped text and target expression are returned as a 2-element list). - - ignore - (default= ``None``) used to define grammars (typically quoted strings and - comments) that might contain false matches to the target expression - - failOn - (default= ``None``) define expressions that are not allowed to be - included in the skipped test; if found before the target expression is found, - the SkipTo is not a match - - Example:: - - report = ''' - Outstanding Issues Report - 1 Jan 2000 - - # | Severity | Description | Days Open - -----+----------+-------------------------------------------+----------- - 101 | Critical | Intermittent system crash | 6 - 94 | Cosmetic | Spelling error on Login ('log|n') | 14 - 79 | Minor | System slow when running too many reports | 47 - ''' - integer = Word(nums) - SEP = Suppress('|') - # use SkipTo to simply match everything up until the next SEP - # - ignore quoted strings, so that a '|' character inside a quoted string does not match - # - parse action will call token.strip() for each matched token, i.e., the description body - string_data = SkipTo(SEP, ignore=quotedString) - string_data.setParseAction(tokenMap(str.strip)) - ticket_expr = (integer("issue_num") + SEP - + string_data("sev") + SEP - + string_data("desc") + SEP - + integer("days_open")) - - for tkt in ticket_expr.searchString(report): - print tkt.dump() - - prints:: - - ['101', 'Critical', 'Intermittent system crash', '6'] - - days_open: 6 - - desc: Intermittent system crash - - issue_num: 101 - - sev: Critical - ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14'] - - days_open: 14 - - desc: Spelling error on Login ('log|n') - - issue_num: 94 - - sev: Cosmetic - ['79', 'Minor', 'System slow when running too many reports', '47'] - - days_open: 47 - - desc: System slow when running too many reports - - issue_num: 79 - - sev: Minor - """ - def __init__(self, other, include=False, ignore=None, failOn=None): - super(SkipTo, self).__init__(other) - self.ignoreExpr = ignore - self.mayReturnEmpty = True - self.mayIndexError = False - self.includeMatch = include - self.saveAsList = False - if isinstance(failOn, basestring): - self.failOn = self._literalStringClass(failOn) - else: - self.failOn = failOn - self.errmsg = "No match found for " + _ustr(self.expr) - - def parseImpl(self, instring, loc, doActions=True): - startloc = loc - instrlen = len(instring) - expr = self.expr - expr_parse = self.expr._parse - self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None - self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None - - tmploc = loc - while tmploc <= instrlen: - if self_failOn_canParseNext is not None: - # break if failOn expression matches - if self_failOn_canParseNext(instring, tmploc): - break - - if self_ignoreExpr_tryParse is not None: - # advance past ignore expressions - while 1: - try: - tmploc = self_ignoreExpr_tryParse(instring, tmploc) - except ParseBaseException: - break - - try: - expr_parse(instring, tmploc, doActions=False, callPreParse=False) - except (ParseException, IndexError): - # no match, advance loc in string - tmploc += 1 - else: - # matched skipto expr, done - break - - else: - # ran off the end of the input string without matching skipto expr, fail - raise ParseException(instring, loc, self.errmsg, self) - - # build up return values - loc = tmploc - skiptext = instring[startloc:loc] - skipresult = ParseResults(skiptext) - - if self.includeMatch: - loc, mat = expr_parse(instring, loc, doActions, callPreParse=False) - skipresult += mat - - return loc, skipresult - -class Forward(ParseElementEnhance): - """Forward declaration of an expression to be defined later - - used for recursive grammars, such as algebraic infix notation. - When the expression is known, it is assigned to the ``Forward`` - variable using the '<<' operator. - - Note: take care when assigning to ``Forward`` not to overlook - precedence of operators. - - Specifically, '|' has a lower precedence than '<<', so that:: - - fwdExpr << a | b | c - - will actually be evaluated as:: - - (fwdExpr << a) | b | c - - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the ``Forward``:: - - fwdExpr << (a | b | c) - - Converting to use the '<<=' operator instead will avoid this problem. - - See :class:`ParseResults.pprint` for an example of a recursive - parser created using ``Forward``. - """ - def __init__(self, other=None): - super(Forward, self).__init__(other, savelist=False) - - def __lshift__(self, other): - if isinstance(other, basestring): - other = self._literalStringClass(other) - self.expr = other - self.strRepr = None - self.mayIndexError = self.expr.mayIndexError - self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars(self.expr.whiteChars) - self.skipWhitespace = self.expr.skipWhitespace - self.saveAsList = self.expr.saveAsList - self.ignoreExprs.extend(self.expr.ignoreExprs) - return self - - def __ilshift__(self, other): - return self << other - - def leaveWhitespace(self): - self.skipWhitespace = False - return self - - def streamline(self): - if not self.streamlined: - self.streamlined = True - if self.expr is not None: - self.expr.streamline() - return self - - def validate(self, validateTrace=None): - if validateTrace is None: - validateTrace = [] - - if self not in validateTrace: - tmp = validateTrace[:] + [self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__(self): - if hasattr(self, "name"): - return self.name - if self.strRepr is not None: - return self.strRepr - - # Avoid infinite recursion by setting a temporary strRepr - self.strRepr = ": ..." - - # Use the string representation of main expression. - retString = '...' - try: - if self.expr is not None: - retString = _ustr(self.expr)[:1000] - else: - retString = "None" - finally: - self.strRepr = self.__class__.__name__ + ": " + retString - return self.strRepr - - def copy(self): - if self.expr is not None: - return super(Forward, self).copy() - else: - ret = Forward() - ret <<= self - return ret - - def _setResultsName(self, name, listAllMatches=False): - if __diag__.warn_name_set_on_empty_Forward: - if self.expr is None: - warnings.warn("{0}: setting results name {0!r} on {1} expression " - "that has no contained expression".format("warn_name_set_on_empty_Forward", - name, - type(self).__name__), - stacklevel=3) - - return super(Forward, self)._setResultsName(name, listAllMatches) - -class TokenConverter(ParseElementEnhance): - """ - Abstract subclass of :class:`ParseExpression`, for converting parsed results. - """ - def __init__(self, expr, savelist=False): - super(TokenConverter, self).__init__(expr) # , savelist) - self.saveAsList = False - -class Combine(TokenConverter): - """Converter to concatenate all matching tokens to a single string. - By default, the matching patterns must also be contiguous in the - input string; this can be disabled by specifying - ``'adjacent=False'`` in the constructor. - - Example:: - - real = Word(nums) + '.' + Word(nums) - print(real.parseString('3.1416')) # -> ['3', '.', '1416'] - # will also erroneously match the following - print(real.parseString('3. 1416')) # -> ['3', '.', '1416'] - - real = Combine(Word(nums) + '.' + Word(nums)) - print(real.parseString('3.1416')) # -> ['3.1416'] - # no match when there are internal spaces - print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...) - """ - def __init__(self, expr, joinString="", adjacent=True): - super(Combine, self).__init__(expr) - # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself - if adjacent: - self.leaveWhitespace() - self.adjacent = adjacent - self.skipWhitespace = True - self.joinString = joinString - self.callPreparse = True - - def ignore(self, other): - if self.adjacent: - ParserElement.ignore(self, other) - else: - super(Combine, self).ignore(other) - return self - - def postParse(self, instring, loc, tokenlist): - retToks = tokenlist.copy() - del retToks[:] - retToks += ParseResults(["".join(tokenlist._asStringList(self.joinString))], modal=self.modalResults) - - if self.resultsName and retToks.haskeys(): - return [retToks] - else: - return retToks - -class Group(TokenConverter): - """Converter to return the matched tokens as a list - useful for - returning tokens of :class:`ZeroOrMore` and :class:`OneOrMore` expressions. - - Example:: - - ident = Word(alphas) - num = Word(nums) - term = ident | num - func = ident + Optional(delimitedList(term)) - print(func.parseString("fn a, b, 100")) # -> ['fn', 'a', 'b', '100'] - - func = ident + Group(Optional(delimitedList(term))) - print(func.parseString("fn a, b, 100")) # -> ['fn', ['a', 'b', '100']] - """ - def __init__(self, expr): - super(Group, self).__init__(expr) - self.saveAsList = True - - def postParse(self, instring, loc, tokenlist): - return [tokenlist] - -class Dict(TokenConverter): - """Converter to return a repetitive expression as a list, but also - as a dictionary. Each element can also be referenced using the first - token in the expression as its key. Useful for tabular report - scraping when the first column can be used as a item key. - - Example:: - - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) - - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - - # print attributes as plain groups - print(OneOrMore(attr_expr).parseString(text).dump()) - - # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names - result = Dict(OneOrMore(Group(attr_expr))).parseString(text) - print(result.dump()) - - # access named fields as dict entries, or output as dict - print(result['shape']) - print(result.asDict()) - - prints:: - - ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: light blue - - posn: upper left - - shape: SQUARE - - texture: burlap - SQUARE - {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} - - See more examples at :class:`ParseResults` of accessing fields by results name. - """ - def __init__(self, expr): - super(Dict, self).__init__(expr) - self.saveAsList = True - - def postParse(self, instring, loc, tokenlist): - for i, tok in enumerate(tokenlist): - if len(tok) == 0: - continue - ikey = tok[0] - if isinstance(ikey, int): - ikey = _ustr(tok[0]).strip() - if len(tok) == 1: - tokenlist[ikey] = _ParseResultsWithOffset("", i) - elif len(tok) == 2 and not isinstance(tok[1], ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i) - else: - dictvalue = tok.copy() # ParseResults(i) - del dictvalue[0] - if len(dictvalue) != 1 or (isinstance(dictvalue, ParseResults) and dictvalue.haskeys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue, i) - else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i) - - if self.resultsName: - return [tokenlist] - else: - return tokenlist - - -class Suppress(TokenConverter): - """Converter for ignoring the results of a parsed expression. - - Example:: - - source = "a, b, c,d" - wd = Word(alphas) - wd_list1 = wd + ZeroOrMore(',' + wd) - print(wd_list1.parseString(source)) - - # often, delimiters that are useful during parsing are just in the - # way afterward - use Suppress to keep them out of the parsed output - wd_list2 = wd + ZeroOrMore(Suppress(',') + wd) - print(wd_list2.parseString(source)) - - prints:: - - ['a', ',', 'b', ',', 'c', ',', 'd'] - ['a', 'b', 'c', 'd'] - - (See also :class:`delimitedList`.) - """ - def postParse(self, instring, loc, tokenlist): - return [] - - def suppress(self): - return self - - -class OnlyOnce(object): - """Wrapper for parse actions, to ensure they are only called once. - """ - def __init__(self, methodCall): - self.callable = _trim_arity(methodCall) - self.called = False - def __call__(self, s, l, t): - if not self.called: - results = self.callable(s, l, t) - self.called = True - return results - raise ParseException(s, l, "") - def reset(self): - self.called = False - -def traceParseAction(f): - """Decorator for debugging parse actions. - - When the parse action is called, this decorator will print - ``">> entering method-name(line:, , )"``. - When the parse action completes, the decorator will print - ``"<<"`` followed by the returned value, or any exception that the parse action raised. - - Example:: - - wd = Word(alphas) - - @traceParseAction - def remove_duplicate_chars(tokens): - return ''.join(sorted(set(''.join(tokens)))) - - wds = OneOrMore(wd).setParseAction(remove_duplicate_chars) - print(wds.parseString("slkdjs sld sldd sdlf sdljf")) - - prints:: - - >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) - < 3: - thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write(">>entering %s(line: '%s', %d, %r)\n" % (thisFunc, line(l, s), l, t)) - try: - ret = f(*paArgs) - except Exception as exc: - sys.stderr.write("< ['aa', 'bb', 'cc'] - delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] - """ - dlName = _ustr(expr) + " [" + _ustr(delim) + " " + _ustr(expr) + "]..." - if combine: - return Combine(expr + ZeroOrMore(delim + expr)).setName(dlName) - else: - return (expr + ZeroOrMore(Suppress(delim) + expr)).setName(dlName) - -def countedArray(expr, intExpr=None): - """Helper to define a counted list of expressions. - - This helper defines a pattern of the form:: - - integer expr expr expr... - - where the leading integer tells how many expr expressions follow. - The matched tokens returns the array of expr tokens as a list - the - leading count token is suppressed. - - If ``intExpr`` is specified, it should be a pyparsing expression - that produces an integer value. - - Example:: - - countedArray(Word(alphas)).parseString('2 ab cd ef') # -> ['ab', 'cd'] - - # in this parser, the leading integer value is given in binary, - # '10' indicating that 2 values are in the array - binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2)) - countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd'] - """ - arrayExpr = Forward() - def countFieldParseAction(s, l, t): - n = t[0] - arrayExpr << (n and Group(And([expr] * n)) or Group(empty)) - return [] - if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t: int(t[0])) - else: - intExpr = intExpr.copy() - intExpr.setName("arrayLen") - intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return (intExpr + arrayExpr).setName('(len) ' + _ustr(expr) + '...') - -def _flatten(L): - ret = [] - for i in L: - if isinstance(i, list): - ret.extend(_flatten(i)) - else: - ret.append(i) - return ret - -def matchPreviousLiteral(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks for - a 'repeat' of a previous expression. For example:: - - first = Word(nums) - second = matchPreviousLiteral(first) - matchExpr = first + ":" + second - - will match ``"1:1"``, but not ``"1:2"``. Because this - matches a previous literal, will also match the leading - ``"1:1"`` in ``"1:10"``. If this is not desired, use - :class:`matchPreviousExpr`. Do *not* use with packrat parsing - enabled. - """ - rep = Forward() - def copyTokenToRepeater(s, l, t): - if t: - if len(t) == 1: - rep << t[0] - else: - # flatten t tokens - tflat = _flatten(t.asList()) - rep << And(Literal(tt) for tt in tflat) - else: - rep << Empty() - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - rep.setName('(prev) ' + _ustr(expr)) - return rep - -def matchPreviousExpr(expr): - """Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks for - a 'repeat' of a previous expression. For example:: - - first = Word(nums) - second = matchPreviousExpr(first) - matchExpr = first + ":" + second - - will match ``"1:1"``, but not ``"1:2"``. Because this - matches by expressions, will *not* match the leading ``"1:1"`` - in ``"1:10"``; the expressions are evaluated first, and then - compared, so ``"1"`` is compared with ``"10"``. Do *not* use - with packrat parsing enabled. - """ - rep = Forward() - e2 = expr.copy() - rep <<= e2 - def copyTokenToRepeater(s, l, t): - matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s, l, t): - theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException('', 0, '') - rep.setParseAction(mustMatchTheseTokens, callDuringTry=True) - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - rep.setName('(prev) ' + _ustr(expr)) - return rep - -def _escapeRegexRangeChars(s): - # ~ escape these chars: ^-[] - for c in r"\^-[]": - s = s.replace(c, _bslash + c) - s = s.replace("\n", r"\n") - s = s.replace("\t", r"\t") - return _ustr(s) - -def oneOf(strs, caseless=False, useRegex=True, asKeyword=False): - """Helper to quickly define a set of alternative Literals, and makes - sure to do longest-first testing when there is a conflict, - regardless of the input order, but returns - a :class:`MatchFirst` for best performance. - - Parameters: - - - strs - a string of space-delimited literals, or a collection of - string literals - - caseless - (default= ``False``) - treat all literals as - caseless - - useRegex - (default= ``True``) - as an optimization, will - generate a Regex object; otherwise, will generate - a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if - creating a :class:`Regex` raises an exception) - - asKeyword - (default=``False``) - enforce Keyword-style matching on the - generated expressions - - Example:: - - comp_oper = oneOf("< = > <= >= !=") - var = Word(alphas) - number = Word(nums) - term = var | number - comparison_expr = term + comp_oper + term - print(comparison_expr.searchString("B = 12 AA=23 B<=AA AA>12")) - - prints:: - - [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] - """ - if isinstance(caseless, basestring): - warnings.warn("More than one string argument passed to oneOf, pass " - "choices as a list or space-delimited string", stacklevel=2) - - if caseless: - isequal = (lambda a, b: a.upper() == b.upper()) - masks = (lambda a, b: b.upper().startswith(a.upper())) - parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral - else: - isequal = (lambda a, b: a == b) - masks = (lambda a, b: b.startswith(a)) - parseElementClass = Keyword if asKeyword else Literal - - symbols = [] - if isinstance(strs, basestring): - symbols = strs.split() - elif isinstance(strs, Iterable): - symbols = list(strs) - else: - warnings.warn("Invalid argument to oneOf, expected string or iterable", - SyntaxWarning, stacklevel=2) - if not symbols: - return NoMatch() - - if not asKeyword: - # if not producing keywords, need to reorder to take care to avoid masking - # longer choices with shorter ones - i = 0 - while i < len(symbols) - 1: - cur = symbols[i] - for j, other in enumerate(symbols[i + 1:]): - if isequal(other, cur): - del symbols[i + j + 1] - break - elif masks(cur, other): - del symbols[i + j + 1] - symbols.insert(i, other) - break - else: - i += 1 - - if not (caseless or asKeyword) and useRegex: - # ~ print (strs, "->", "|".join([_escapeRegexChars(sym) for sym in symbols])) - try: - if len(symbols) == len("".join(symbols)): - return Regex("[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols)).setName(' | '.join(symbols)) - else: - return Regex("|".join(re.escape(sym) for sym in symbols)).setName(' | '.join(symbols)) - except Exception: - warnings.warn("Exception creating Regex for oneOf, building MatchFirst", - SyntaxWarning, stacklevel=2) - - # last resort, just use MatchFirst - return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols)) - -def dictOf(key, value): - """Helper to easily and clearly define a dictionary by specifying - the respective patterns for the key and value. Takes care of - defining the :class:`Dict`, :class:`ZeroOrMore`, and - :class:`Group` tokens in the proper order. The key pattern - can include delimiting markers or punctuation, as long as they are - suppressed, thereby leaving the significant key text. The value - pattern can include named results, so that the :class:`Dict` results - can include named token fields. - - Example:: - - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - print(OneOrMore(attr_expr).parseString(text).dump()) - - attr_label = label - attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join) - - # similar to Dict, but simpler call format - result = dictOf(attr_label, attr_value).parseString(text) - print(result.dump()) - print(result['shape']) - print(result.shape) # object attribute access works too - print(result.asDict()) - - prints:: - - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: light blue - - posn: upper left - - shape: SQUARE - - texture: burlap - SQUARE - SQUARE - {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} - """ - return Dict(OneOrMore(Group(key + value))) - -def originalTextFor(expr, asString=True): - """Helper to return the original, untokenized text for a given - expression. Useful to restore the parsed fields of an HTML start - tag into the raw tag text itself, or to revert separate tokens with - intervening whitespace back to the original matching input text. By - default, returns astring containing the original parsed text. - - If the optional ``asString`` argument is passed as - ``False``, then the return value is - a :class:`ParseResults` containing any results names that - were originally matched, and a single token containing the original - matched text from the input string. So if the expression passed to - :class:`originalTextFor` contains expressions with defined - results names, you must set ``asString`` to ``False`` if you - want to preserve those results name values. - - Example:: - - src = "this is test bold text normal text " - for tag in ("b", "i"): - opener, closer = makeHTMLTags(tag) - patt = originalTextFor(opener + SkipTo(closer) + closer) - print(patt.searchString(src)[0]) - - prints:: - - [' bold text '] - ['text'] - """ - locMarker = Empty().setParseAction(lambda s, loc, t: loc) - endlocMarker = locMarker.copy() - endlocMarker.callPreparse = False - matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") - if asString: - extractText = lambda s, l, t: s[t._original_start: t._original_end] - else: - def extractText(s, l, t): - t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]] - matchExpr.setParseAction(extractText) - matchExpr.ignoreExprs = expr.ignoreExprs - return matchExpr - -def ungroup(expr): - """Helper to undo pyparsing's default grouping of And expressions, - even if all but one are non-empty. - """ - return TokenConverter(expr).addParseAction(lambda t: t[0]) - -def locatedExpr(expr): - """Helper to decorate a returned token with its starting and ending - locations in the input string. - - This helper adds the following results names: - - - locn_start = location where matched expression begins - - locn_end = location where matched expression ends - - value = the actual parsed results - - Be careful if the input text contains ```` characters, you - may want to call :class:`ParserElement.parseWithTabs` - - Example:: - - wd = Word(alphas) - for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"): - print(match) - - prints:: - - [[0, 'ljsdf', 5]] - [[8, 'lksdjjf', 15]] - [[18, 'lkkjj', 23]] - """ - locator = Empty().setParseAction(lambda s, l, t: l) - return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end")) - - -# convenience constants for positional expressions -empty = Empty().setName("empty") -lineStart = LineStart().setName("lineStart") -lineEnd = LineEnd().setName("lineEnd") -stringStart = StringStart().setName("stringStart") -stringEnd = StringEnd().setName("stringEnd") - -_escapedPunc = Word(_bslash, r"\[]-*.$+^?()~ ", exact=2).setParseAction(lambda s, l, t: t[0][1]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s, l, t: unichr(int(t[0].lstrip(r'\0x'), 16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s, l, t: unichr(int(t[0][1:], 8))) -_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1) -_charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group(OneOrMore(_charRange | _singleChar)).setResultsName("body") + "]" - -def srange(s): - r"""Helper to easily define string ranges for use in Word - construction. Borrows syntax from regexp '[]' string range - definitions:: - - srange("[0-9]") -> "0123456789" - srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" - srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" - - The input string must be enclosed in []'s, and the returned string - is the expanded character set joined into a single string. The - values enclosed in the []'s may be: - - - a single character - - an escaped character with a leading backslash (such as ``\-`` - or ``\]``) - - an escaped hex character with a leading ``'\x'`` - (``\x21``, which is a ``'!'`` character) (``\0x##`` - is also supported for backwards compatibility) - - an escaped octal character with a leading ``'\0'`` - (``\041``, which is a ``'!'`` character) - - a range of any of the above, separated by a dash (``'a-z'``, - etc.) - - any combination of the above (``'aeiouy'``, - ``'a-zA-Z0-9_$'``, etc.) - """ - _expanded = lambda p: p if not isinstance(p, ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]), ord(p[1]) + 1)) - try: - return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body) - except Exception: - return "" - -def matchOnlyAtCol(n): - """Helper method for defining parse actions that require matching at - a specific column in the input text. - """ - def verifyCol(strg, locn, toks): - if col(locn, strg) != n: - raise ParseException(strg, locn, "matched token not at column %d" % n) - return verifyCol - -def replaceWith(replStr): - """Helper method for common parse actions that simply return - a literal value. Especially useful when used with - :class:`transformString` (). - - Example:: - - num = Word(nums).setParseAction(lambda toks: int(toks[0])) - na = oneOf("N/A NA").setParseAction(replaceWith(math.nan)) - term = na | num - - OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234] - """ - return lambda s, l, t: [replStr] - -def removeQuotes(s, l, t): - """Helper parse action for removing quotation marks from parsed - quoted strings. - - Example:: - - # by default, quotation marks are included in parsed results - quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] - - # use removeQuotes to strip quotation marks from parsed results - quotedString.setParseAction(removeQuotes) - quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] - """ - return t[0][1:-1] - -def tokenMap(func, *args): - """Helper to define a parse action by mapping a function to all - elements of a ParseResults list. If any additional args are passed, - they are forwarded to the given function as additional arguments - after the token, as in - ``hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))``, - which will convert the parsed data to an integer using base 16. - - Example (compare the last to example in :class:`ParserElement.transformString`:: - - hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16)) - hex_ints.runTests(''' - 00 11 22 aa FF 0a 0d 1a - ''') - - upperword = Word(alphas).setParseAction(tokenMap(str.upper)) - OneOrMore(upperword).runTests(''' - my kingdom for a horse - ''') - - wd = Word(alphas).setParseAction(tokenMap(str.title)) - OneOrMore(wd).setParseAction(' '.join).runTests(''' - now is the winter of our discontent made glorious summer by this sun of york - ''') - - prints:: - - 00 11 22 aa FF 0a 0d 1a - [0, 17, 34, 170, 255, 10, 13, 26] - - my kingdom for a horse - ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE'] - - now is the winter of our discontent made glorious summer by this sun of york - ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] - """ - def pa(s, l, t): - return [func(tokn, *args) for tokn in t] - - try: - func_name = getattr(func, '__name__', - getattr(func, '__class__').__name__) - except Exception: - func_name = str(func) - pa.__name__ = func_name - - return pa - -upcaseTokens = tokenMap(lambda t: _ustr(t).upper()) -"""(Deprecated) Helper parse action to convert tokens to upper case. -Deprecated in favor of :class:`pyparsing_common.upcaseTokens`""" - -downcaseTokens = tokenMap(lambda t: _ustr(t).lower()) -"""(Deprecated) Helper parse action to convert tokens to lower case. -Deprecated in favor of :class:`pyparsing_common.downcaseTokens`""" - -def _makeTags(tagStr, xml, - suppress_LT=Suppress("<"), - suppress_GT=Suppress(">")): - """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr, basestring): - resname = tagStr - tagStr = Keyword(tagStr, caseless=not xml) - else: - resname = tagStr.name - - tagAttrName = Word(alphas, alphanums + "_-:") - if xml: - tagAttrValue = dblQuotedString.copy().setParseAction(removeQuotes) - openTag = (suppress_LT - + tagStr("tag") - + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) - + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/') - + suppress_GT) - else: - tagAttrValue = quotedString.copy().setParseAction(removeQuotes) | Word(printables, excludeChars=">") - openTag = (suppress_LT - + tagStr("tag") - + Dict(ZeroOrMore(Group(tagAttrName.setParseAction(downcaseTokens) - + Optional(Suppress("=") + tagAttrValue)))) - + Optional("/", default=[False])("empty").setParseAction(lambda s, l, t: t[0] == '/') - + suppress_GT) - closeTag = Combine(_L("", adjacent=False) - - openTag.setName("<%s>" % resname) - # add start results name in parse action now that ungrouped names are not reported at two levels - openTag.addParseAction(lambda t: t.__setitem__("start" + "".join(resname.replace(":", " ").title().split()), t.copy())) - closeTag = closeTag("end" + "".join(resname.replace(":", " ").title().split())).setName("" % resname) - openTag.tag = resname - closeTag.tag = resname - openTag.tag_body = SkipTo(closeTag()) - return openTag, closeTag - -def makeHTMLTags(tagStr): - """Helper to construct opening and closing tag expressions for HTML, - given a tag name. Matches tags in either upper or lower case, - attributes with namespaces and with quoted or unquoted values. - - Example:: - - text = 'More info at the pyparsing wiki page' - # makeHTMLTags returns pyparsing expressions for the opening and - # closing tags as a 2-tuple - a, a_end = makeHTMLTags("A") - link_expr = a + SkipTo(a_end)("link_text") + a_end - - for link in link_expr.searchString(text): - # attributes in the tag (like "href" shown here) are - # also accessible as named results - print(link.link_text, '->', link.href) - - prints:: - - pyparsing -> https://github.com/pyparsing/pyparsing/wiki - """ - return _makeTags(tagStr, False) - -def makeXMLTags(tagStr): - """Helper to construct opening and closing tag expressions for XML, - given a tag name. Matches tags only in the given upper/lower case. - - Example: similar to :class:`makeHTMLTags` - """ - return _makeTags(tagStr, True) - -def withAttribute(*args, **attrDict): - """Helper to create a validating parse action to be used with start - tags created with :class:`makeXMLTags` or - :class:`makeHTMLTags`. Use ``withAttribute`` to qualify - a starting tag with a required attribute value, to avoid false - matches on common tags such as ```` or ``
``. - - Call ``withAttribute`` with a series of attribute names and - values. Specify the list of filter attributes names and values as: - - - keyword arguments, as in ``(align="right")``, or - - as an explicit dict with ``**`` operator, when an attribute - name is also a Python reserved word, as in ``**{"class":"Customer", "align":"right"}`` - - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align", "right"))`` - - For attribute names with a namespace prefix, you must use the second - form. Attribute names are matched insensitive to upper/lower case. - - If just testing for ``class`` (with or without a namespace), use - :class:`withClass`. - - To verify that the attribute exists, but without specifying a value, - pass ``withAttribute.ANY_VALUE`` as the value. - - Example:: - - html = ''' -
- Some text -
1 4 0 1 0
-
1,3 2,3 1,1
-
this has no type
-
- - ''' - div,div_end = makeHTMLTags("div") - - # only match div tag having a type attribute with value "grid" - div_grid = div().setParseAction(withAttribute(type="grid")) - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.searchString(html): - print(grid_header.body) - - # construct a match with any div tag having a type attribute, regardless of the value - div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.searchString(html): - print(div_header.body) - - prints:: - - 1 4 0 1 0 - - 1 4 0 1 0 - 1,3 2,3 1,1 - """ - if args: - attrs = args[:] - else: - attrs = attrDict.items() - attrs = [(k, v) for k, v in attrs] - def pa(s, l, tokens): - for attrName, attrValue in attrs: - if attrName not in tokens: - raise ParseException(s, l, "no matching attribute " + attrName) - if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s, l, "attribute '%s' has value '%s', must be '%s'" % - (attrName, tokens[attrName], attrValue)) - return pa -withAttribute.ANY_VALUE = object() - -def withClass(classname, namespace=''): - """Simplified version of :class:`withAttribute` when - matching on a div class - made difficult because ``class`` is - a reserved word in Python. - - Example:: - - html = ''' -
- Some text -
1 4 0 1 0
-
1,3 2,3 1,1
-
this <div> has no class
-
- - ''' - div,div_end = makeHTMLTags("div") - div_grid = div().setParseAction(withClass("grid")) - - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.searchString(html): - print(grid_header.body) - - div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.searchString(html): - print(div_header.body) - - prints:: - - 1 4 0 1 0 - - 1 4 0 1 0 - 1,3 2,3 1,1 - """ - classattr = "%s:class" % namespace if namespace else "class" - return withAttribute(**{classattr: classname}) - -opAssoc = SimpleNamespace() -opAssoc.LEFT = object() -opAssoc.RIGHT = object() - -def infixNotation(baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')')): - """Helper method for constructing grammars of expressions made up of - operators working in a precedence hierarchy. Operators may be unary - or binary, left- or right-associative. Parse actions can also be - attached to operator expressions. The generated parser will also - recognize the use of parentheses to override operator precedences - (see example below). - - Note: if you define a deep operator list, you may see performance - issues when using infixNotation. See - :class:`ParserElement.enablePackrat` for a mechanism to potentially - improve your parser performance. - - Parameters: - - baseExpr - expression representing the most basic element for the - nested - - opList - list of tuples, one for each operator precedence level - in the expression grammar; each tuple is of the form ``(opExpr, - numTerms, rightLeftAssoc, parseAction)``, where: - - - opExpr is the pyparsing expression for the operator; may also - be a string, which will be converted to a Literal; if numTerms - is 3, opExpr is a tuple of two expressions, for the two - operators separating the 3 terms - - numTerms is the number of terms for this operator (must be 1, - 2, or 3) - - rightLeftAssoc is the indicator whether the operator is right - or left associative, using the pyparsing-defined constants - ``opAssoc.RIGHT`` and ``opAssoc.LEFT``. - - parseAction is the parse action to be associated with - expressions matching this operator expression (the parse action - tuple member may be omitted); if the parse action is passed - a tuple or list of functions, this is equivalent to calling - ``setParseAction(*fn)`` - (:class:`ParserElement.setParseAction`) - - lpar - expression for matching left-parentheses - (default= ``Suppress('(')``) - - rpar - expression for matching right-parentheses - (default= ``Suppress(')')``) - - Example:: - - # simple example of four-function arithmetic with ints and - # variable names - integer = pyparsing_common.signed_integer - varname = pyparsing_common.identifier - - arith_expr = infixNotation(integer | varname, - [ - ('-', 1, opAssoc.RIGHT), - (oneOf('* /'), 2, opAssoc.LEFT), - (oneOf('+ -'), 2, opAssoc.LEFT), - ]) - - arith_expr.runTests(''' - 5+3*6 - (5+3)*6 - -2--11 - ''', fullDump=False) - - prints:: - - 5+3*6 - [[5, '+', [3, '*', 6]]] - - (5+3)*6 - [[[5, '+', 3], '*', 6]] - - -2--11 - [[['-', 2], '-', ['-', 11]]] - """ - # captive version of FollowedBy that does not do parse actions or capture results names - class _FB(FollowedBy): - def parseImpl(self, instring, loc, doActions=True): - self.expr.tryParse(instring, loc) - return loc, [] - - ret = Forward() - lastExpr = baseExpr | (lpar + ret + rpar) - for i, operDef in enumerate(opList): - opExpr, arity, rightLeftAssoc, pa = (operDef + (None, ))[:4] - termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr - if arity == 3: - if opExpr is None or len(opExpr) != 2: - raise ValueError( - "if numterms=3, opExpr must be a tuple or list of two expressions") - opExpr1, opExpr2 = opExpr - thisExpr = Forward().setName(termName) - if rightLeftAssoc == opAssoc.LEFT: - if arity == 1: - matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + OneOrMore(opExpr)) - elif arity == 2: - if opExpr is not None: - matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group(lastExpr + OneOrMore(opExpr + lastExpr)) - else: - matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr + OneOrMore(lastExpr)) - elif arity == 3: - matchExpr = (_FB(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) - + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - elif rightLeftAssoc == opAssoc.RIGHT: - if arity == 1: - # try to avoid LR with this extra test - if not isinstance(opExpr, Optional): - opExpr = Optional(opExpr) - matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) - elif arity == 2: - if opExpr is not None: - matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group(lastExpr + OneOrMore(opExpr + thisExpr)) - else: - matchExpr = _FB(lastExpr + thisExpr) + Group(lastExpr + OneOrMore(thisExpr)) - elif arity == 3: - matchExpr = (_FB(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) - + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr)) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - else: - raise ValueError("operator must indicate right or left associativity") - if pa: - if isinstance(pa, (tuple, list)): - matchExpr.setParseAction(*pa) - else: - matchExpr.setParseAction(pa) - thisExpr <<= (matchExpr.setName(termName) | lastExpr) - lastExpr = thisExpr - ret <<= lastExpr - return ret - -operatorPrecedence = infixNotation -"""(Deprecated) Former name of :class:`infixNotation`, will be -dropped in a future release.""" - -dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"').setName("string enclosed in double quotes") -sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("string enclosed in single quotes") -quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"' - | Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'").setName("quotedString using single or double quotes") -unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") - -def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): - """Helper method for defining nested lists enclosed in opening and - closing delimiters ("(" and ")" are the default). - - Parameters: - - opener - opening character for a nested list - (default= ``"("``); can also be a pyparsing expression - - closer - closing character for a nested list - (default= ``")"``); can also be a pyparsing expression - - content - expression for items within the nested lists - (default= ``None``) - - ignoreExpr - expression for ignoring opening and closing - delimiters (default= :class:`quotedString`) - - If an expression is not provided for the content argument, the - nested expression will capture all whitespace-delimited content - between delimiters as a list of separate values. - - Use the ``ignoreExpr`` argument to define expressions that may - contain opening or closing characters that should not be treated as - opening or closing characters for nesting, such as quotedString or - a comment expression. Specify multiple expressions using an - :class:`Or` or :class:`MatchFirst`. The default is - :class:`quotedString`, but if no expressions are to be ignored, then - pass ``None`` for this argument. - - Example:: - - data_type = oneOf("void int short long char float double") - decl_data_type = Combine(data_type + Optional(Word('*'))) - ident = Word(alphas+'_', alphanums+'_') - number = pyparsing_common.number - arg = Group(decl_data_type + ident) - LPAR, RPAR = map(Suppress, "()") - - code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) - - c_function = (decl_data_type("type") - + ident("name") - + LPAR + Optional(delimitedList(arg), [])("args") + RPAR - + code_body("body")) - c_function.ignore(cStyleComment) - - source_code = ''' - int is_odd(int x) { - return (x%2); - } - - int dec_to_hex(char hchar) { - if (hchar >= '0' && hchar <= '9') { - return (ord(hchar)-ord('0')); - } else { - return (10+ord(hchar)-ord('A')); - } - } - ''' - for func in c_function.searchString(source_code): - print("%(name)s (%(type)s) args: %(args)s" % func) - - - prints:: - - is_odd (int) args: [['int', 'x']] - dec_to_hex (int) args: [['char', 'hchar']] - """ - if opener == closer: - raise ValueError("opening and closing strings cannot be the same") - if content is None: - if isinstance(opener, basestring) and isinstance(closer, basestring): - if len(opener) == 1 and len(closer) == 1: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr - + CharsNotIn(opener - + closer - + ParserElement.DEFAULT_WHITE_CHARS, exact=1) - ) - ).setParseAction(lambda t: t[0].strip())) - else: - content = (empty.copy() + CharsNotIn(opener - + closer - + ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t: t[0].strip())) - else: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr - + ~Literal(opener) - + ~Literal(closer) - + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)) - ).setParseAction(lambda t: t[0].strip())) - else: - content = (Combine(OneOrMore(~Literal(opener) - + ~Literal(closer) - + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1)) - ).setParseAction(lambda t: t[0].strip())) - else: - raise ValueError("opening and closing arguments must be strings if no content expression is given") - ret = Forward() - if ignoreExpr is not None: - ret <<= Group(Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer)) - else: - ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer)) - ret.setName('nested %s%s expression' % (opener, closer)) - return ret - -def indentedBlock(blockStatementExpr, indentStack, indent=True): - """Helper method for defining space-delimited indentation blocks, - such as those used to define block statements in Python source code. - - Parameters: - - - blockStatementExpr - expression defining syntax of statement that - is repeated within the indented block - - indentStack - list created by caller to manage indentation stack - (multiple statementWithIndentedBlock expressions within a single - grammar should share a common indentStack) - - indent - boolean indicating whether block must be indented beyond - the current level; set to False for block of left-most - statements (default= ``True``) - - A valid block must contain at least one ``blockStatement``. - - Example:: - - data = ''' - def A(z): - A1 - B = 100 - G = A2 - A2 - A3 - B - def BB(a,b,c): - BB1 - def BBA(): - bba1 - bba2 - bba3 - C - D - def spam(x,y): - def eggs(z): - pass - ''' - - - indentStack = [1] - stmt = Forward() - - identifier = Word(alphas, alphanums) - funcDecl = ("def" + identifier + Group("(" + Optional(delimitedList(identifier)) + ")") + ":") - func_body = indentedBlock(stmt, indentStack) - funcDef = Group(funcDecl + func_body) - - rvalue = Forward() - funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") - rvalue << (funcCall | identifier | Word(nums)) - assignment = Group(identifier + "=" + rvalue) - stmt << (funcDef | assignment | identifier) - - module_body = OneOrMore(stmt) - - parseTree = module_body.parseString(data) - parseTree.pprint() - - prints:: - - [['def', - 'A', - ['(', 'z', ')'], - ':', - [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], - 'B', - ['def', - 'BB', - ['(', 'a', 'b', 'c', ')'], - ':', - [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], - 'C', - 'D', - ['def', - 'spam', - ['(', 'x', 'y', ')'], - ':', - [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] - """ - backup_stack = indentStack[:] - - def reset_stack(): - indentStack[:] = backup_stack - - def checkPeerIndent(s, l, t): - if l >= len(s): return - curCol = col(l, s) - if curCol != indentStack[-1]: - if curCol > indentStack[-1]: - raise ParseException(s, l, "illegal nesting") - raise ParseException(s, l, "not a peer entry") - - def checkSubIndent(s, l, t): - curCol = col(l, s) - if curCol > indentStack[-1]: - indentStack.append(curCol) - else: - raise ParseException(s, l, "not a subentry") - - def checkUnindent(s, l, t): - if l >= len(s): return - curCol = col(l, s) - if not(indentStack and curCol in indentStack): - raise ParseException(s, l, "not an unindent") - if curCol < indentStack[-1]: - indentStack.pop() - - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress(), stopOn=StringEnd()) - INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') - PEER = Empty().setParseAction(checkPeerIndent).setName('') - UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') - if indent: - smExpr = Group(Optional(NL) - + INDENT - + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd()) - + UNDENT) - else: - smExpr = Group(Optional(NL) - + OneOrMore(PEER + Group(blockStatementExpr) + Optional(NL), stopOn=StringEnd()) - + UNDENT) - smExpr.setFailAction(lambda a, b, c, d: reset_stack()) - blockStatementExpr.ignore(_bslash + LineEnd()) - return smExpr.setName('indented block') - -alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") -punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") - -anyOpenTag, anyCloseTag = makeHTMLTags(Word(alphas, alphanums + "_:").setName('any tag')) -_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(), '><& "\'')) -commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") -def replaceHTMLEntity(t): - """Helper parser action to replace common HTML entities with their special characters""" - return _htmlEntityMap.get(t.entity) - -# it's easy to get these comment structures wrong - they're very common, so may as well make them available -cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") -"Comment of the form ``/* ... */``" - -htmlComment = Regex(r"").setName("HTML comment") -"Comment of the form ````" - -restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") -dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") -"Comment of the form ``// ... (to end of line)``" - -cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/' | dblSlashComment).setName("C++ style comment") -"Comment of either form :class:`cStyleComment` or :class:`dblSlashComment`" - -javaStyleComment = cppStyleComment -"Same as :class:`cppStyleComment`" - -pythonStyleComment = Regex(r"#.*").setName("Python style comment") -"Comment of the form ``# ... (to end of line)``" - -_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') - + Optional(Word(" \t") - + ~Literal(",") + ~LineEnd()))).streamline().setName("commaItem") -commaSeparatedList = delimitedList(Optional(quotedString.copy() | _commasepitem, default="")).setName("commaSeparatedList") -"""(Deprecated) Predefined expression of 1 or more printable words or -quoted strings, separated by commas. - -This expression is deprecated in favor of :class:`pyparsing_common.comma_separated_list`. -""" - -# some other useful expressions - using lower-case class name since we are really using this as a namespace -class pyparsing_common: - """Here are some common low-level expressions that may be useful in - jump-starting parser development: - - - numeric forms (:class:`integers`, :class:`reals`, - :class:`scientific notation`) - - common :class:`programming identifiers` - - network addresses (:class:`MAC`, - :class:`IPv4`, :class:`IPv6`) - - ISO8601 :class:`dates` and - :class:`datetime` - - :class:`UUID` - - :class:`comma-separated list` - - Parse actions: - - - :class:`convertToInteger` - - :class:`convertToFloat` - - :class:`convertToDate` - - :class:`convertToDatetime` - - :class:`stripHTMLTags` - - :class:`upcaseTokens` - - :class:`downcaseTokens` - - Example:: - - pyparsing_common.number.runTests(''' - # any int or real number, returned as the appropriate type - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.fnumber.runTests(''' - # any int or real number, returned as float - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.hex_integer.runTests(''' - # hex numbers - 100 - FF - ''') - - pyparsing_common.fraction.runTests(''' - # fractions - 1/2 - -3/4 - ''') - - pyparsing_common.mixed_integer.runTests(''' - # mixed fractions - 1 - 1/2 - -3/4 - 1-3/4 - ''') - - import uuid - pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) - pyparsing_common.uuid.runTests(''' - # uuid - 12345678-1234-5678-1234-567812345678 - ''') - - prints:: - - # any int or real number, returned as the appropriate type - 100 - [100] - - -100 - [-100] - - +100 - [100] - - 3.14159 - [3.14159] - - 6.02e23 - [6.02e+23] - - 1e-12 - [1e-12] - - # any int or real number, returned as float - 100 - [100.0] - - -100 - [-100.0] - - +100 - [100.0] - - 3.14159 - [3.14159] - - 6.02e23 - [6.02e+23] - - 1e-12 - [1e-12] - - # hex numbers - 100 - [256] - - FF - [255] - - # fractions - 1/2 - [0.5] - - -3/4 - [-0.75] - - # mixed fractions - 1 - [1] - - 1/2 - [0.5] - - -3/4 - [-0.75] - - 1-3/4 - [1.75] - - # uuid - 12345678-1234-5678-1234-567812345678 - [UUID('12345678-1234-5678-1234-567812345678')] - """ - - convertToInteger = tokenMap(int) - """ - Parse action for converting parsed integers to Python int - """ - - convertToFloat = tokenMap(float) - """ - Parse action for converting parsed numbers to Python float - """ - - integer = Word(nums).setName("integer").setParseAction(convertToInteger) - """expression that parses an unsigned integer, returns an int""" - - hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int, 16)) - """expression that parses a hexadecimal integer, returns an int""" - - signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) - """expression that parses an integer with optional leading sign, returns an int""" - - fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") - """fractional expression of an integer divided by an integer, returns a float""" - fraction.addParseAction(lambda t: t[0]/t[-1]) - - mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") - """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" - mixed_integer.addParseAction(sum) - - real = Regex(r'[+-]?(?:\d+\.\d*|\.\d+)').setName("real number").setParseAction(convertToFloat) - """expression that parses a floating point number and returns a float""" - - sci_real = Regex(r'[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) - """expression that parses a floating point number with optional - scientific notation and returns a float""" - - # streamlining this expression makes the docs nicer-looking - number = (sci_real | real | signed_integer).streamline() - """any numeric expression, returns the corresponding Python type""" - - fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) - """any int or real number, returned as float""" - - identifier = Word(alphas + '_', alphanums + '_').setName("identifier") - """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" - - ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") - "IPv4 address (``0.0.0.0 - 255.255.255.255``)" - - _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") - _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part) * 7).setName("full IPv6 address") - _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6)) - + "::" - + Optional(_ipv6_part + (':' + _ipv6_part) * (0, 6)) - ).setName("short IPv6 address") - _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) - _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") - ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") - "IPv6 address (long, short, or mixed form)" - - mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") - "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" - - @staticmethod - def convertToDate(fmt="%Y-%m-%d"): - """ - Helper to create a parse action for converting parsed date string to Python datetime.date - - Params - - - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%d"``) - - Example:: - - date_expr = pyparsing_common.iso8601_date.copy() - date_expr.setParseAction(pyparsing_common.convertToDate()) - print(date_expr.parseString("1999-12-31")) - - prints:: - - [datetime.date(1999, 12, 31)] - """ - def cvt_fn(s, l, t): - try: - return datetime.strptime(t[0], fmt).date() - except ValueError as ve: - raise ParseException(s, l, str(ve)) - return cvt_fn - - @staticmethod - def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): - """Helper to create a parse action for converting parsed - datetime string to Python datetime.datetime - - Params - - - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%dT%H:%M:%S.%f"``) - - Example:: - - dt_expr = pyparsing_common.iso8601_datetime.copy() - dt_expr.setParseAction(pyparsing_common.convertToDatetime()) - print(dt_expr.parseString("1999-12-31T23:59:59.999")) - - prints:: - - [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] - """ - def cvt_fn(s, l, t): - try: - return datetime.strptime(t[0], fmt) - except ValueError as ve: - raise ParseException(s, l, str(ve)) - return cvt_fn - - iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") - "ISO8601 date (``yyyy-mm-dd``)" - - iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") - "ISO8601 datetime (``yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)``) - trailing seconds, milliseconds, and timezone optional; accepts separating ``'T'`` or ``' '``" - - uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") - "UUID (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``)" - - _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() - @staticmethod - def stripHTMLTags(s, l, tokens): - """Parse action to remove HTML tags from web page HTML source - - Example:: - - # strip HTML links from normal text - text = 'More info at the
pyparsing wiki page' - td, td_end = makeHTMLTags("TD") - table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end - print(table_text.parseString(text).body) - - Prints:: - - More info at the pyparsing wiki page - """ - return pyparsing_common._html_stripper.transformString(tokens[0]) - - _commasepitem = Combine(OneOrMore(~Literal(",") - + ~LineEnd() - + Word(printables, excludeChars=',') - + Optional(White(" \t")))).streamline().setName("commaItem") - comma_separated_list = delimitedList(Optional(quotedString.copy() - | _commasepitem, default='') - ).setName("comma separated list") - """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" - - upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) - """Parse action to convert tokens to upper case.""" - - downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) - """Parse action to convert tokens to lower case.""" - - -class _lazyclassproperty(object): - def __init__(self, fn): - self.fn = fn - self.__doc__ = fn.__doc__ - self.__name__ = fn.__name__ - - def __get__(self, obj, cls): - if cls is None: - cls = type(obj) - if not hasattr(cls, '_intern') or any(cls._intern is getattr(superclass, '_intern', []) - for superclass in cls.__mro__[1:]): - cls._intern = {} - attrname = self.fn.__name__ - if attrname not in cls._intern: - cls._intern[attrname] = self.fn(cls) - return cls._intern[attrname] - - -class unicode_set(object): - """ - A set of Unicode characters, for language-specific strings for - ``alphas``, ``nums``, ``alphanums``, and ``printables``. - A unicode_set is defined by a list of ranges in the Unicode character - set, in a class attribute ``_ranges``, such as:: - - _ranges = [(0x0020, 0x007e), (0x00a0, 0x00ff),] - - A unicode set can also be defined using multiple inheritance of other unicode sets:: - - class CJK(Chinese, Japanese, Korean): - pass - """ - _ranges = [] - - @classmethod - def _get_chars_for_ranges(cls): - ret = [] - for cc in cls.__mro__: - if cc is unicode_set: - break - for rr in cc._ranges: - ret.extend(range(rr[0], rr[-1] + 1)) - return [unichr(c) for c in sorted(set(ret))] - - @_lazyclassproperty - def printables(cls): - "all non-whitespace characters in this range" - return u''.join(filterfalse(unicode.isspace, cls._get_chars_for_ranges())) - - @_lazyclassproperty - def alphas(cls): - "all alphabetic characters in this range" - return u''.join(filter(unicode.isalpha, cls._get_chars_for_ranges())) - - @_lazyclassproperty - def nums(cls): - "all numeric digit characters in this range" - return u''.join(filter(unicode.isdigit, cls._get_chars_for_ranges())) - - @_lazyclassproperty - def alphanums(cls): - "all alphanumeric characters in this range" - return cls.alphas + cls.nums - - -class pyparsing_unicode(unicode_set): - """ - A namespace class for defining common language unicode_sets. - """ - _ranges = [(32, sys.maxunicode)] - - class Latin1(unicode_set): - "Unicode set for Latin-1 Unicode Character Range" - _ranges = [(0x0020, 0x007e), (0x00a0, 0x00ff),] - - class LatinA(unicode_set): - "Unicode set for Latin-A Unicode Character Range" - _ranges = [(0x0100, 0x017f),] - - class LatinB(unicode_set): - "Unicode set for Latin-B Unicode Character Range" - _ranges = [(0x0180, 0x024f),] - - class Greek(unicode_set): - "Unicode set for Greek Unicode Character Ranges" - _ranges = [ - (0x0370, 0x03ff), (0x1f00, 0x1f15), (0x1f18, 0x1f1d), (0x1f20, 0x1f45), (0x1f48, 0x1f4d), - (0x1f50, 0x1f57), (0x1f59,), (0x1f5b,), (0x1f5d,), (0x1f5f, 0x1f7d), (0x1f80, 0x1fb4), (0x1fb6, 0x1fc4), - (0x1fc6, 0x1fd3), (0x1fd6, 0x1fdb), (0x1fdd, 0x1fef), (0x1ff2, 0x1ff4), (0x1ff6, 0x1ffe), - ] - - class Cyrillic(unicode_set): - "Unicode set for Cyrillic Unicode Character Range" - _ranges = [(0x0400, 0x04ff)] - - class Chinese(unicode_set): - "Unicode set for Chinese Unicode Character Range" - _ranges = [(0x4e00, 0x9fff), (0x3000, 0x303f),] - - class Japanese(unicode_set): - "Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges" - _ranges = [] - - class Kanji(unicode_set): - "Unicode set for Kanji Unicode Character Range" - _ranges = [(0x4E00, 0x9Fbf), (0x3000, 0x303f),] - - class Hiragana(unicode_set): - "Unicode set for Hiragana Unicode Character Range" - _ranges = [(0x3040, 0x309f),] - - class Katakana(unicode_set): - "Unicode set for Katakana Unicode Character Range" - _ranges = [(0x30a0, 0x30ff),] - - class Korean(unicode_set): - "Unicode set for Korean Unicode Character Range" - _ranges = [(0xac00, 0xd7af), (0x1100, 0x11ff), (0x3130, 0x318f), (0xa960, 0xa97f), (0xd7b0, 0xd7ff), (0x3000, 0x303f),] - - class CJK(Chinese, Japanese, Korean): - "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range" - pass - - class Thai(unicode_set): - "Unicode set for Thai Unicode Character Range" - _ranges = [(0x0e01, 0x0e3a), (0x0e3f, 0x0e5b),] - - class Arabic(unicode_set): - "Unicode set for Arabic Unicode Character Range" - _ranges = [(0x0600, 0x061b), (0x061e, 0x06ff), (0x0700, 0x077f),] - - class Hebrew(unicode_set): - "Unicode set for Hebrew Unicode Character Range" - _ranges = [(0x0590, 0x05ff),] - - class Devanagari(unicode_set): - "Unicode set for Devanagari Unicode Character Range" - _ranges = [(0x0900, 0x097f), (0xa8e0, 0xa8ff)] - -pyparsing_unicode.Japanese._ranges = (pyparsing_unicode.Japanese.Kanji._ranges - + pyparsing_unicode.Japanese.Hiragana._ranges - + pyparsing_unicode.Japanese.Katakana._ranges) - -# define ranges in language character sets -if PY_3: - setattr(pyparsing_unicode, u"العربية", pyparsing_unicode.Arabic) - setattr(pyparsing_unicode, u"中文", pyparsing_unicode.Chinese) - setattr(pyparsing_unicode, u"кириллица", pyparsing_unicode.Cyrillic) - setattr(pyparsing_unicode, u"Ελληνικά", pyparsing_unicode.Greek) - setattr(pyparsing_unicode, u"עִברִית", pyparsing_unicode.Hebrew) - setattr(pyparsing_unicode, u"日本語", pyparsing_unicode.Japanese) - setattr(pyparsing_unicode.Japanese, u"漢字", pyparsing_unicode.Japanese.Kanji) - setattr(pyparsing_unicode.Japanese, u"カタカナ", pyparsing_unicode.Japanese.Katakana) - setattr(pyparsing_unicode.Japanese, u"ひらがな", pyparsing_unicode.Japanese.Hiragana) - setattr(pyparsing_unicode, u"한국어", pyparsing_unicode.Korean) - setattr(pyparsing_unicode, u"ไทย", pyparsing_unicode.Thai) - setattr(pyparsing_unicode, u"देवनागरी", pyparsing_unicode.Devanagari) - - -class pyparsing_test: - """ - namespace class for classes useful in writing unit tests - """ - - class reset_pyparsing_context: - """ - Context manager to be used when writing unit tests that modify pyparsing config values: - - packrat parsing - - default whitespace characters. - - default keyword characters - - literal string auto-conversion class - - __diag__ settings - - Example: - with reset_pyparsing_context(): - # test that literals used to construct a grammar are automatically suppressed - ParserElement.inlineLiteralsUsing(Suppress) - - term = Word(alphas) | Word(nums) - group = Group('(' + term[...] + ')') - - # assert that the '()' characters are not included in the parsed tokens - self.assertParseAndCheckLisst(group, "(abc 123 def)", ['abc', '123', 'def']) - - # after exiting context manager, literals are converted to Literal expressions again - """ - - def __init__(self): - self._save_context = {} - - def save(self): - self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS - self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS - self._save_context[ - "literal_string_class" - ] = ParserElement._literalStringClass - self._save_context["packrat_enabled"] = ParserElement._packratEnabled - self._save_context["packrat_parse"] = ParserElement._parse - self._save_context["__diag__"] = { - name: getattr(__diag__, name) for name in __diag__._all_names - } - self._save_context["__compat__"] = { - "collect_all_And_tokens": __compat__.collect_all_And_tokens - } - return self - - def restore(self): - # reset pyparsing global state - if ( - ParserElement.DEFAULT_WHITE_CHARS - != self._save_context["default_whitespace"] - ): - ParserElement.setDefaultWhitespaceChars( - self._save_context["default_whitespace"] - ) - Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] - ParserElement.inlineLiteralsUsing( - self._save_context["literal_string_class"] - ) - for name, value in self._save_context["__diag__"].items(): - setattr(__diag__, name, value) - ParserElement._packratEnabled = self._save_context["packrat_enabled"] - ParserElement._parse = self._save_context["packrat_parse"] - __compat__.collect_all_And_tokens = self._save_context["__compat__"] - - def __enter__(self): - return self.save() - - def __exit__(self, *args): - return self.restore() - - class TestParseResultsAsserts: - """ - A mixin class to add parse results assertion methods to normal unittest.TestCase classes. - """ - def assertParseResultsEquals( - self, result, expected_list=None, expected_dict=None, msg=None - ): - """ - Unit test assertion to compare a ParseResults object with an optional expected_list, - and compare any defined results names with an optional expected_dict. - """ - if expected_list is not None: - self.assertEqual(expected_list, result.asList(), msg=msg) - if expected_dict is not None: - self.assertEqual(expected_dict, result.asDict(), msg=msg) - - def assertParseAndCheckList( - self, expr, test_string, expected_list, msg=None, verbose=True - ): - """ - Convenience wrapper assert to test a parser element and input string, and assert that - the resulting ParseResults.asList() is equal to the expected_list. - """ - result = expr.parseString(test_string, parseAll=True) - if verbose: - print(result.dump()) - self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) - - def assertParseAndCheckDict( - self, expr, test_string, expected_dict, msg=None, verbose=True - ): - """ - Convenience wrapper assert to test a parser element and input string, and assert that - the resulting ParseResults.asDict() is equal to the expected_dict. - """ - result = expr.parseString(test_string, parseAll=True) - if verbose: - print(result.dump()) - self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) - - def assertRunTestResults( - self, run_tests_report, expected_parse_results=None, msg=None - ): - """ - Unit test assertion to evaluate output of ParserElement.runTests(). If a list of - list-dict tuples is given as the expected_parse_results argument, then these are zipped - with the report tuples returned by runTests and evaluated using assertParseResultsEquals. - Finally, asserts that the overall runTests() success value is True. - - :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests - :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] - """ - run_test_success, run_test_results = run_tests_report - - if expected_parse_results is not None: - merged = [ - (rpt[0], rpt[1], expected) - for rpt, expected in zip(run_test_results, expected_parse_results) - ] - for test_string, result, expected in merged: - # expected should be a tuple containing a list and/or a dict or an exception, - # and optional failure message string - # an empty tuple will skip any result validation - fail_msg = next( - (exp for exp in expected if isinstance(exp, str)), None - ) - expected_exception = next( - ( - exp - for exp in expected - if isinstance(exp, type) and issubclass(exp, Exception) - ), - None, - ) - if expected_exception is not None: - with self.assertRaises( - expected_exception=expected_exception, msg=fail_msg or msg - ): - if isinstance(result, Exception): - raise result - else: - expected_list = next( - (exp for exp in expected if isinstance(exp, list)), None - ) - expected_dict = next( - (exp for exp in expected if isinstance(exp, dict)), None - ) - if (expected_list, expected_dict) != (None, None): - self.assertParseResultsEquals( - result, - expected_list=expected_list, - expected_dict=expected_dict, - msg=fail_msg or msg, - ) - else: - # warning here maybe? - print("no validation for {!r}".format(test_string)) - - # do this last, in case some specific test results can be reported instead - self.assertTrue( - run_test_success, msg=msg if msg is not None else "failed runTests" - ) - - @contextmanager - def assertRaisesParseException(self, exc_type=ParseException, msg=None): - with self.assertRaises(exc_type, msg=msg): - yield - - -if __name__ == "__main__": - - selectToken = CaselessLiteral("select") - fromToken = CaselessLiteral("from") - - ident = Word(alphas, alphanums + "_$") - - columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) - columnNameList = Group(delimitedList(columnName)).setName("columns") - columnSpec = ('*' | columnNameList) - - tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) - tableNameList = Group(delimitedList(tableName)).setName("tables") - - simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") - - # demo runTests method, including embedded comments in test string - simpleSQL.runTests(""" - # '*' as column list and dotted table name - select * from SYS.XYZZY - - # caseless match on "SELECT", and casts back to "select" - SELECT * from XYZZY, ABC - - # list of column names, and mixed case SELECT keyword - Select AA,BB,CC from Sys.dual - - # multiple tables - Select A, B, C from Sys.dual, Table2 - - # invalid SELECT keyword - should fail - Xelect A, B, C from Sys.dual - - # incomplete command - should fail - Select - - # invalid column name - should fail - Select ^^^ frox Sys.dual - - """) - - pyparsing_common.number.runTests(""" - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - """) - - # any int or real number, returned as float - pyparsing_common.fnumber.runTests(""" - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - """) - - pyparsing_common.hex_integer.runTests(""" - 100 - FF - """) - - import uuid - pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) - pyparsing_common.uuid.runTests(""" - 12345678-1234-5678-1234-567812345678 - """) diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing.LICENSE b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/LICENSE similarity index 100% rename from conda_lock/_vendor/poetry/core/_vendor/pyparsing.LICENSE rename to conda_lock/_vendor/poetry/core/_vendor/pyparsing/LICENSE diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/__init__.py new file mode 100644 index 000000000..7802ff158 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/__init__.py @@ -0,0 +1,331 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2022 Paul T. McGuire +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = """ +pyparsing module - Classes and methods to define and execute parsing grammars +============================================================================= + +The pyparsing module is an alternative approach to creating and +executing simple grammars, vs. the traditional lex/yacc approach, or the +use of regular expressions. With pyparsing, you don't need to learn +a new syntax for defining grammars or matching expressions - the parsing +module provides a library of classes that you use to construct the +grammar directly in Python. + +Here is a program to parse "Hello, World!" (or any greeting of the form +``", !"``), built up using :class:`Word`, +:class:`Literal`, and :class:`And` elements +(the :meth:`'+'` operators create :class:`And` expressions, +and the strings are auto-converted to :class:`Literal` expressions):: + + from pyparsing import Word, alphas + + # define grammar of a greeting + greet = Word(alphas) + "," + Word(alphas) + "!" + + hello = "Hello, World!" + print(hello, "->", greet.parse_string(hello)) + +The program outputs the following:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the +self-explanatory class names, and the use of :class:`'+'`, +:class:`'|'`, :class:`'^'` and :class:`'&'` operators. + +The :class:`ParseResults` object returned from +:class:`ParserElement.parseString` can be +accessed as a nested list, a dictionary, or an object with named +attributes. + +The pyparsing module handles some of the problems that are typically +vexing when writing text parsers: + + - extra or missing whitespace (the above program will also handle + "Hello,World!", "Hello , World !", etc.) + - quoted strings + - embedded comments + + +Getting Started - +----------------- +Visit the classes :class:`ParserElement` and :class:`ParseResults` to +see the base classes that most other pyparsing +classes inherit from. Use the docstrings for examples of how to: + + - construct literal match expressions from :class:`Literal` and + :class:`CaselessLiteral` classes + - construct character word-group expressions using the :class:`Word` + class + - see how to create repetitive expressions using :class:`ZeroOrMore` + and :class:`OneOrMore` classes + - use :class:`'+'`, :class:`'|'`, :class:`'^'`, + and :class:`'&'` operators to combine simple expressions into + more complex ones + - associate names with your parsed results using + :class:`ParserElement.setResultsName` + - access the parsed data, which is returned as a :class:`ParseResults` + object + - find some helpful expression short-cuts like :class:`delimitedList` + and :class:`oneOf` + - find more useful common expressions in the :class:`pyparsing_common` + namespace class +""" +from typing import NamedTuple + + +class version_info(NamedTuple): + major: int + minor: int + micro: int + releaselevel: str + serial: int + + @property + def __version__(self): + return ( + "{}.{}.{}".format(self.major, self.minor, self.micro) + + ( + "{}{}{}".format( + "r" if self.releaselevel[0] == "c" else "", + self.releaselevel[0], + self.serial, + ), + "", + )[self.releaselevel == "final"] + ) + + def __str__(self): + return "{} {} / {}".format(__name__, self.__version__, __version_time__) + + def __repr__(self): + return "{}.{}({})".format( + __name__, + type(self).__name__, + ", ".join("{}={!r}".format(*nv) for nv in zip(self._fields, self)), + ) + + +__version_info__ = version_info(3, 0, 9, "final", 0) +__version_time__ = "05 May 2022 07:02 UTC" +__version__ = __version_info__.__version__ +__versionTime__ = __version_time__ +__author__ = "Paul McGuire " + +from .util import * +from .exceptions import * +from .actions import * +from .core import __diag__, __compat__ +from .results import * +from .core import * +from .core import _builtin_exprs as core_builtin_exprs +from .helpers import * +from .helpers import _builtin_exprs as helper_builtin_exprs + +from .unicode import unicode_set, UnicodeRangeList, pyparsing_unicode as unicode +from .testing import pyparsing_test as testing +from .common import ( + pyparsing_common as common, + _builtin_exprs as common_builtin_exprs, +) + +# define backward compat synonyms +if "pyparsing_unicode" not in globals(): + pyparsing_unicode = unicode +if "pyparsing_common" not in globals(): + pyparsing_common = common +if "pyparsing_test" not in globals(): + pyparsing_test = testing + +core_builtin_exprs += common_builtin_exprs + helper_builtin_exprs + + +__all__ = [ + "__version__", + "__version_time__", + "__author__", + "__compat__", + "__diag__", + "And", + "AtLineStart", + "AtStringStart", + "CaselessKeyword", + "CaselessLiteral", + "CharsNotIn", + "Combine", + "Dict", + "Each", + "Empty", + "FollowedBy", + "Forward", + "GoToColumn", + "Group", + "IndentedBlock", + "Keyword", + "LineEnd", + "LineStart", + "Literal", + "Located", + "PrecededBy", + "MatchFirst", + "NoMatch", + "NotAny", + "OneOrMore", + "OnlyOnce", + "OpAssoc", + "Opt", + "Optional", + "Or", + "ParseBaseException", + "ParseElementEnhance", + "ParseException", + "ParseExpression", + "ParseFatalException", + "ParseResults", + "ParseSyntaxException", + "ParserElement", + "PositionToken", + "QuotedString", + "RecursiveGrammarException", + "Regex", + "SkipTo", + "StringEnd", + "StringStart", + "Suppress", + "Token", + "TokenConverter", + "White", + "Word", + "WordEnd", + "WordStart", + "ZeroOrMore", + "Char", + "alphanums", + "alphas", + "alphas8bit", + "any_close_tag", + "any_open_tag", + "c_style_comment", + "col", + "common_html_entity", + "counted_array", + "cpp_style_comment", + "dbl_quoted_string", + "dbl_slash_comment", + "delimited_list", + "dict_of", + "empty", + "hexnums", + "html_comment", + "identchars", + "identbodychars", + "java_style_comment", + "line", + "line_end", + "line_start", + "lineno", + "make_html_tags", + "make_xml_tags", + "match_only_at_col", + "match_previous_expr", + "match_previous_literal", + "nested_expr", + "null_debug_action", + "nums", + "one_of", + "printables", + "punc8bit", + "python_style_comment", + "quoted_string", + "remove_quotes", + "replace_with", + "replace_html_entity", + "rest_of_line", + "sgl_quoted_string", + "srange", + "string_end", + "string_start", + "trace_parse_action", + "unicode_string", + "with_attribute", + "indentedBlock", + "original_text_for", + "ungroup", + "infix_notation", + "locatedExpr", + "with_class", + "CloseMatch", + "token_map", + "pyparsing_common", + "pyparsing_unicode", + "unicode_set", + "condition_as_parse_action", + "pyparsing_test", + # pre-PEP8 compatibility names + "__versionTime__", + "anyCloseTag", + "anyOpenTag", + "cStyleComment", + "commonHTMLEntity", + "countedArray", + "cppStyleComment", + "dblQuotedString", + "dblSlashComment", + "delimitedList", + "dictOf", + "htmlComment", + "javaStyleComment", + "lineEnd", + "lineStart", + "makeHTMLTags", + "makeXMLTags", + "matchOnlyAtCol", + "matchPreviousExpr", + "matchPreviousLiteral", + "nestedExpr", + "nullDebugAction", + "oneOf", + "opAssoc", + "pythonStyleComment", + "quotedString", + "removeQuotes", + "replaceHTMLEntity", + "replaceWith", + "restOfLine", + "sglQuotedString", + "stringEnd", + "stringStart", + "traceParseAction", + "unicodeString", + "withAttribute", + "indentedBlock", + "originalTextFor", + "infixNotation", + "locatedExpr", + "withClass", + "tokenMap", + "conditionAsParseAction", + "autoname_elements", +] diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/actions.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/actions.py new file mode 100644 index 000000000..f72c66e74 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/actions.py @@ -0,0 +1,207 @@ +# actions.py + +from .exceptions import ParseException +from .util import col + + +class OnlyOnce: + """ + Wrapper for parse actions, to ensure they are only called once. + """ + + def __init__(self, method_call): + from .core import _trim_arity + + self.callable = _trim_arity(method_call) + self.called = False + + def __call__(self, s, l, t): + if not self.called: + results = self.callable(s, l, t) + self.called = True + return results + raise ParseException(s, l, "OnlyOnce obj called multiple times w/out reset") + + def reset(self): + """ + Allow the associated parse action to be called once more. + """ + + self.called = False + + +def match_only_at_col(n): + """ + Helper method for defining parse actions that require matching at + a specific column in the input text. + """ + + def verify_col(strg, locn, toks): + if col(locn, strg) != n: + raise ParseException(strg, locn, "matched token not at column {}".format(n)) + + return verify_col + + +def replace_with(repl_str): + """ + Helper method for common parse actions that simply return + a literal value. Especially useful when used with + :class:`transform_string` (). + + Example:: + + num = Word(nums).set_parse_action(lambda toks: int(toks[0])) + na = one_of("N/A NA").set_parse_action(replace_with(math.nan)) + term = na | num + + term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234] + """ + return lambda s, l, t: [repl_str] + + +def remove_quotes(s, l, t): + """ + Helper parse action for removing quotation marks from parsed + quoted strings. + + Example:: + + # by default, quotation marks are included in parsed results + quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] + + # use remove_quotes to strip quotation marks from parsed results + quoted_string.set_parse_action(remove_quotes) + quoted_string.parse_string("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] + """ + return t[0][1:-1] + + +def with_attribute(*args, **attr_dict): + """ + Helper to create a validating parse action to be used with start + tags created with :class:`make_xml_tags` or + :class:`make_html_tags`. Use ``with_attribute`` to qualify + a starting tag with a required attribute value, to avoid false + matches on common tags such as ```` or ``
``. + + Call ``with_attribute`` with a series of attribute names and + values. Specify the list of filter attributes names and values as: + + - keyword arguments, as in ``(align="right")``, or + - as an explicit dict with ``**`` operator, when an attribute + name is also a Python reserved word, as in ``**{"class":"Customer", "align":"right"}`` + - a list of name-value tuples, as in ``(("ns1:class", "Customer"), ("ns2:align", "right"))`` + + For attribute names with a namespace prefix, you must use the second + form. Attribute names are matched insensitive to upper/lower case. + + If just testing for ``class`` (with or without a namespace), use + :class:`with_class`. + + To verify that the attribute exists, but without specifying a value, + pass ``with_attribute.ANY_VALUE`` as the value. + + Example:: + + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = make_html_tags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().set_parse_action(with_attribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.search_string(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().set_parse_action(with_attribute(type=with_attribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.search_string(html): + print(div_header.body) + + prints:: + + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attr_dict.items() + attrs = [(k, v) for k, v in attrs] + + def pa(s, l, tokens): + for attrName, attrValue in attrs: + if attrName not in tokens: + raise ParseException(s, l, "no matching attribute " + attrName) + if attrValue != with_attribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException( + s, + l, + "attribute {!r} has value {!r}, must be {!r}".format( + attrName, tokens[attrName], attrValue + ), + ) + + return pa + + +with_attribute.ANY_VALUE = object() + + +def with_class(classname, namespace=""): + """ + Simplified version of :class:`with_attribute` when + matching on a div class - made difficult because ``class`` is + a reserved word in Python. + + Example:: + + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = make_html_tags("div") + div_grid = div().set_parse_action(with_class("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.search_string(html): + print(grid_header.body) + + div_any_type = div().set_parse_action(with_class(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.search_string(html): + print(div_header.body) + + prints:: + + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "{}:class".format(namespace) if namespace else "class" + return with_attribute(**{classattr: classname}) + + +# pre-PEP8 compatibility symbols +replaceWith = replace_with +removeQuotes = remove_quotes +withAttribute = with_attribute +withClass = with_class +matchOnlyAtCol = match_only_at_col diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/common.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/common.py new file mode 100644 index 000000000..1859fb79c --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/common.py @@ -0,0 +1,424 @@ +# common.py +from .core import * +from .helpers import delimited_list, any_open_tag, any_close_tag +from datetime import datetime + + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """Here are some common low-level expressions that may be useful in + jump-starting parser development: + + - numeric forms (:class:`integers`, :class:`reals`, + :class:`scientific notation`) + - common :class:`programming identifiers` + - network addresses (:class:`MAC`, + :class:`IPv4`, :class:`IPv6`) + - ISO8601 :class:`dates` and + :class:`datetime` + - :class:`UUID` + - :class:`comma-separated list` + - :class:`url` + + Parse actions: + + - :class:`convertToInteger` + - :class:`convertToFloat` + - :class:`convertToDate` + - :class:`convertToDatetime` + - :class:`stripHTMLTags` + - :class:`upcaseTokens` + - :class:`downcaseTokens` + + Example:: + + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + + prints:: + + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convert_to_integer = token_map(int) + """ + Parse action for converting parsed integers to Python int + """ + + convert_to_float = token_map(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).set_name("integer").set_parse_action(convert_to_integer) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = ( + Word(hexnums).set_name("hex integer").set_parse_action(token_map(int, 16)) + ) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = ( + Regex(r"[+-]?\d+") + .set_name("signed integer") + .set_parse_action(convert_to_integer) + ) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = ( + signed_integer().set_parse_action(convert_to_float) + + "/" + + signed_integer().set_parse_action(convert_to_float) + ).set_name("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.add_parse_action(lambda tt: tt[0] / tt[-1]) + + mixed_integer = ( + fraction | signed_integer + Opt(Opt("-").suppress() + fraction) + ).set_name("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.add_parse_action(sum) + + real = ( + Regex(r"[+-]?(?:\d+\.\d*|\.\d+)") + .set_name("real number") + .set_parse_action(convert_to_float) + ) + """expression that parses a floating point number and returns a float""" + + sci_real = ( + Regex(r"[+-]?(?:\d+(?:[eE][+-]?\d+)|(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?)") + .set_name("real number with scientific notation") + .set_parse_action(convert_to_float) + ) + """expression that parses a floating point number with optional + scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).setName("number").streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = ( + Regex(r"[+-]?\d+\.?\d*([eE][+-]?\d+)?") + .set_name("fnumber") + .set_parse_action(convert_to_float) + ) + """any int or real number, returned as float""" + + identifier = Word(identchars, identbodychars).set_name("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex( + r"(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}" + ).set_name("IPv4 address") + "IPv4 address (``0.0.0.0 - 255.255.255.255``)" + + _ipv6_part = Regex(r"[0-9a-fA-F]{1,4}").set_name("hex_integer") + _full_ipv6_address = (_ipv6_part + (":" + _ipv6_part) * 7).set_name( + "full IPv6 address" + ) + _short_ipv6_address = ( + Opt(_ipv6_part + (":" + _ipv6_part) * (0, 6)) + + "::" + + Opt(_ipv6_part + (":" + _ipv6_part) * (0, 6)) + ).set_name("short IPv6 address") + _short_ipv6_address.add_condition( + lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8 + ) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).set_name("mixed IPv6 address") + ipv6_address = Combine( + (_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).set_name( + "IPv6 address" + ) + ).set_name("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex( + r"[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}" + ).set_name("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convert_to_date(fmt: str = "%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%d"``) + + Example:: + + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + + prints:: + + [datetime.date(1999, 12, 31)] + """ + + def cvt_fn(ss, ll, tt): + try: + return datetime.strptime(tt[0], fmt).date() + except ValueError as ve: + raise ParseException(ss, ll, str(ve)) + + return cvt_fn + + @staticmethod + def convert_to_datetime(fmt: str = "%Y-%m-%dT%H:%M:%S.%f"): + """Helper to create a parse action for converting parsed + datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default= ``"%Y-%m-%dT%H:%M:%S.%f"``) + + Example:: + + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + + prints:: + + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + + def cvt_fn(s, l, t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + + return cvt_fn + + iso8601_date = Regex( + r"(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?" + ).set_name("ISO8601 date") + "ISO8601 date (``yyyy-mm-dd``)" + + iso8601_datetime = Regex( + r"(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?" + ).set_name("ISO8601 datetime") + "ISO8601 datetime (``yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)``) - trailing seconds, milliseconds, and timezone optional; accepts separating ``'T'`` or ``' '``" + + uuid = Regex(r"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}").set_name("UUID") + "UUID (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``)" + + _html_stripper = any_open_tag.suppress() | any_close_tag.suppress() + + @staticmethod + def strip_html_tags(s: str, l: int, tokens: ParseResults): + """Parse action to remove HTML tags from web page HTML source + + Example:: + + # strip HTML links from normal text + text = 'More info at the pyparsing wiki page' + td, td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + print(table_text.parseString(text).body) + + Prints:: + + More info at the pyparsing wiki page + """ + return pyparsing_common._html_stripper.transform_string(tokens[0]) + + _commasepitem = ( + Combine( + OneOrMore( + ~Literal(",") + + ~LineEnd() + + Word(printables, exclude_chars=",") + + Opt(White(" \t") + ~FollowedBy(LineEnd() | ",")) + ) + ) + .streamline() + .set_name("commaItem") + ) + comma_separated_list = delimited_list( + Opt(quoted_string.copy() | _commasepitem, default="") + ).set_name("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcase_tokens = staticmethod(token_map(lambda t: t.upper())) + """Parse action to convert tokens to upper case.""" + + downcase_tokens = staticmethod(token_map(lambda t: t.lower())) + """Parse action to convert tokens to lower case.""" + + # fmt: off + url = Regex( + # https://mathiasbynens.be/demo/url-regex + # https://gist.github.com/dperini/729294 + r"^" + + # protocol identifier (optional) + # short syntax // still required + r"(?:(?:(?Phttps?|ftp):)?\/\/)" + + # user:pass BasicAuth (optional) + r"(?:(?P\S+(?::\S*)?)@)?" + + r"(?P" + + # IP address exclusion + # private & local networks + r"(?!(?:10|127)(?:\.\d{1,3}){3})" + + r"(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})" + + r"(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})" + + # IP address dotted notation octets + # excludes loopback network 0.0.0.0 + # excludes reserved space >= 224.0.0.0 + # excludes network & broadcast addresses + # (first & last IP address of each class) + r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])" + + r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}" + + r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))" + + r"|" + + # host & domain names, may end with dot + # can be replaced by a shortest alternative + # (?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.)+ + r"(?:" + + r"(?:" + + r"[a-z0-9\u00a1-\uffff]" + + r"[a-z0-9\u00a1-\uffff_-]{0,62}" + + r")?" + + r"[a-z0-9\u00a1-\uffff]\." + + r")+" + + # TLD identifier name, may end with dot + r"(?:[a-z\u00a1-\uffff]{2,}\.?)" + + r")" + + # port number (optional) + r"(:(?P\d{2,5}))?" + + # resource path (optional) + r"(?P\/[^?# ]*)?" + + # query string (optional) + r"(\?(?P[^#]*))?" + + # fragment (optional) + r"(#(?P\S*))?" + + r"$" + ).set_name("url") + # fmt: on + + # pre-PEP8 compatibility names + convertToInteger = convert_to_integer + convertToFloat = convert_to_float + convertToDate = convert_to_date + convertToDatetime = convert_to_datetime + stripHTMLTags = strip_html_tags + upcaseTokens = upcase_tokens + downcaseTokens = downcase_tokens + + +_builtin_exprs = [ + v for v in vars(pyparsing_common).values() if isinstance(v, ParserElement) +] diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/core.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/core.py new file mode 100644 index 000000000..9acba3f3e --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/core.py @@ -0,0 +1,5814 @@ +# +# core.py +# +import os +import typing +from typing import ( + NamedTuple, + Union, + Callable, + Any, + Generator, + Tuple, + List, + TextIO, + Set, + Sequence, +) +from abc import ABC, abstractmethod +from enum import Enum +import string +import copy +import warnings +import re +import sys +from collections.abc import Iterable +import traceback +import types +from operator import itemgetter +from functools import wraps +from threading import RLock +from pathlib import Path + +from .util import ( + _FifoCache, + _UnboundedCache, + __config_flags, + _collapse_string_to_ranges, + _escape_regex_range_chars, + _bslash, + _flatten, + LRUMemo as _LRUMemo, + UnboundedMemo as _UnboundedMemo, +) +from .exceptions import * +from .actions import * +from .results import ParseResults, _ParseResultsWithOffset +from .unicode import pyparsing_unicode + +_MAX_INT = sys.maxsize +str_type: Tuple[type, ...] = (str, bytes) + +# +# Copyright (c) 2003-2022 Paul T. McGuire +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + + +if sys.version_info >= (3, 8): + from functools import cached_property +else: + + class cached_property: + def __init__(self, func): + self._func = func + + def __get__(self, instance, owner=None): + ret = instance.__dict__[self._func.__name__] = self._func(instance) + return ret + + +class __compat__(__config_flags): + """ + A cross-version compatibility configuration for pyparsing features that will be + released in a future version. By setting values in this configuration to True, + those features can be enabled in prior versions for compatibility development + and testing. + + - ``collect_all_And_tokens`` - flag to enable fix for Issue #63 that fixes erroneous grouping + of results names when an :class:`And` expression is nested within an :class:`Or` or :class:`MatchFirst`; + maintained for compatibility, but setting to ``False`` no longer restores pre-2.3.1 + behavior + """ + + _type_desc = "compatibility" + + collect_all_And_tokens = True + + _all_names = [__ for __ in locals() if not __.startswith("_")] + _fixed_names = """ + collect_all_And_tokens + """.split() + + +class __diag__(__config_flags): + _type_desc = "diagnostic" + + warn_multiple_tokens_in_named_alternation = False + warn_ungrouped_named_tokens_in_collection = False + warn_name_set_on_empty_Forward = False + warn_on_parse_using_empty_Forward = False + warn_on_assignment_to_Forward = False + warn_on_multiple_string_args_to_oneof = False + warn_on_match_first_with_lshift_operator = False + enable_debug_on_named_expressions = False + + _all_names = [__ for __ in locals() if not __.startswith("_")] + _warning_names = [name for name in _all_names if name.startswith("warn")] + _debug_names = [name for name in _all_names if name.startswith("enable_debug")] + + @classmethod + def enable_all_warnings(cls) -> None: + for name in cls._warning_names: + cls.enable(name) + + +class Diagnostics(Enum): + """ + Diagnostic configuration (all default to disabled) + - ``warn_multiple_tokens_in_named_alternation`` - flag to enable warnings when a results + name is defined on a :class:`MatchFirst` or :class:`Or` expression with one or more :class:`And` subexpressions + - ``warn_ungrouped_named_tokens_in_collection`` - flag to enable warnings when a results + name is defined on a containing expression with ungrouped subexpressions that also + have results names + - ``warn_name_set_on_empty_Forward`` - flag to enable warnings when a :class:`Forward` is defined + with a results name, but has no contents defined + - ``warn_on_parse_using_empty_Forward`` - flag to enable warnings when a :class:`Forward` is + defined in a grammar but has never had an expression attached to it + - ``warn_on_assignment_to_Forward`` - flag to enable warnings when a :class:`Forward` is defined + but is overwritten by assigning using ``'='`` instead of ``'<<='`` or ``'<<'`` + - ``warn_on_multiple_string_args_to_oneof`` - flag to enable warnings when :class:`one_of` is + incorrectly called with multiple str arguments + - ``enable_debug_on_named_expressions`` - flag to auto-enable debug on all subsequent + calls to :class:`ParserElement.set_name` + + Diagnostics are enabled/disabled by calling :class:`enable_diag` and :class:`disable_diag`. + All warnings can be enabled by calling :class:`enable_all_warnings`. + """ + + warn_multiple_tokens_in_named_alternation = 0 + warn_ungrouped_named_tokens_in_collection = 1 + warn_name_set_on_empty_Forward = 2 + warn_on_parse_using_empty_Forward = 3 + warn_on_assignment_to_Forward = 4 + warn_on_multiple_string_args_to_oneof = 5 + warn_on_match_first_with_lshift_operator = 6 + enable_debug_on_named_expressions = 7 + + +def enable_diag(diag_enum: Diagnostics) -> None: + """ + Enable a global pyparsing diagnostic flag (see :class:`Diagnostics`). + """ + __diag__.enable(diag_enum.name) + + +def disable_diag(diag_enum: Diagnostics) -> None: + """ + Disable a global pyparsing diagnostic flag (see :class:`Diagnostics`). + """ + __diag__.disable(diag_enum.name) + + +def enable_all_warnings() -> None: + """ + Enable all global pyparsing diagnostic warnings (see :class:`Diagnostics`). + """ + __diag__.enable_all_warnings() + + +# hide abstract class +del __config_flags + + +def _should_enable_warnings( + cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str] +) -> bool: + enable = bool(warn_env_var) + for warn_opt in cmd_line_warn_options: + w_action, w_message, w_category, w_module, w_line = (warn_opt + "::::").split( + ":" + )[:5] + if not w_action.lower().startswith("i") and ( + not (w_message or w_category or w_module) or w_module == "pyparsing" + ): + enable = True + elif w_action.lower().startswith("i") and w_module in ("pyparsing", ""): + enable = False + return enable + + +if _should_enable_warnings( + sys.warnoptions, os.environ.get("PYPARSINGENABLEALLWARNINGS") +): + enable_all_warnings() + + +# build list of single arg builtins, that can be used as parse actions +_single_arg_builtins = { + sum, + len, + sorted, + reversed, + list, + tuple, + set, + any, + all, + min, + max, +} + +_generatorType = types.GeneratorType +ParseAction = Union[ + Callable[[], Any], + Callable[[ParseResults], Any], + Callable[[int, ParseResults], Any], + Callable[[str, int, ParseResults], Any], +] +ParseCondition = Union[ + Callable[[], bool], + Callable[[ParseResults], bool], + Callable[[int, ParseResults], bool], + Callable[[str, int, ParseResults], bool], +] +ParseFailAction = Callable[[str, int, "ParserElement", Exception], None] +DebugStartAction = Callable[[str, int, "ParserElement", bool], None] +DebugSuccessAction = Callable[ + [str, int, int, "ParserElement", ParseResults, bool], None +] +DebugExceptionAction = Callable[[str, int, "ParserElement", Exception, bool], None] + + +alphas = string.ascii_uppercase + string.ascii_lowercase +identchars = pyparsing_unicode.Latin1.identchars +identbodychars = pyparsing_unicode.Latin1.identbodychars +nums = "0123456789" +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +printables = "".join([c for c in string.printable if c not in string.whitespace]) + +_trim_arity_call_line: traceback.StackSummary = None + + +def _trim_arity(func, max_limit=3): + """decorator to trim function calls to match the arity of the target""" + global _trim_arity_call_line + + if func in _single_arg_builtins: + return lambda s, l, t: func(t) + + limit = 0 + found_arity = False + + def extract_tb(tb, limit=0): + frames = traceback.extract_tb(tb, limit=limit) + frame_summary = frames[-1] + return [frame_summary[:2]] + + # synthesize what would be returned by traceback.extract_stack at the call to + # user's parse action 'func', so that we don't incur call penalty at parse time + + # fmt: off + LINE_DIFF = 7 + # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND + # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! + _trim_arity_call_line = (_trim_arity_call_line or traceback.extract_stack(limit=2)[-1]) + pa_call_line_synth = (_trim_arity_call_line[0], _trim_arity_call_line[1] + LINE_DIFF) + + def wrapper(*args): + nonlocal found_arity, limit + while 1: + try: + ret = func(*args[limit:]) + found_arity = True + return ret + except TypeError as te: + # re-raise TypeErrors if they did not come from our arity testing + if found_arity: + raise + else: + tb = te.__traceback__ + trim_arity_type_error = ( + extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth + ) + del tb + + if trim_arity_type_error: + if limit < max_limit: + limit += 1 + continue + + raise + # fmt: on + + # copy func name to wrapper for sensible debug output + # (can't use functools.wraps, since that messes with function signature) + func_name = getattr(func, "__name__", getattr(func, "__class__").__name__) + wrapper.__name__ = func_name + wrapper.__doc__ = func.__doc__ + + return wrapper + + +def condition_as_parse_action( + fn: ParseCondition, message: str = None, fatal: bool = False +) -> ParseAction: + """ + Function to convert a simple predicate function that returns ``True`` or ``False`` + into a parse action. Can be used in places when a parse action is required + and :class:`ParserElement.add_condition` cannot be used (such as when adding a condition + to an operator level in :class:`infix_notation`). + + Optional keyword arguments: + + - ``message`` - define a custom message to be used in the raised exception + - ``fatal`` - if True, will raise :class:`ParseFatalException` to stop parsing immediately; + otherwise will raise :class:`ParseException` + + """ + msg = message if message is not None else "failed user-defined condition" + exc_type = ParseFatalException if fatal else ParseException + fn = _trim_arity(fn) + + @wraps(fn) + def pa(s, l, t): + if not bool(fn(s, l, t)): + raise exc_type(s, l, msg) + + return pa + + +def _default_start_debug_action( + instring: str, loc: int, expr: "ParserElement", cache_hit: bool = False +): + cache_hit_str = "*" if cache_hit else "" + print( + ( + "{}Match {} at loc {}({},{})\n {}\n {}^".format( + cache_hit_str, + expr, + loc, + lineno(loc, instring), + col(loc, instring), + line(loc, instring), + " " * (col(loc, instring) - 1), + ) + ) + ) + + +def _default_success_debug_action( + instring: str, + startloc: int, + endloc: int, + expr: "ParserElement", + toks: ParseResults, + cache_hit: bool = False, +): + cache_hit_str = "*" if cache_hit else "" + print("{}Matched {} -> {}".format(cache_hit_str, expr, toks.as_list())) + + +def _default_exception_debug_action( + instring: str, + loc: int, + expr: "ParserElement", + exc: Exception, + cache_hit: bool = False, +): + cache_hit_str = "*" if cache_hit else "" + print( + "{}Match {} failed, {} raised: {}".format( + cache_hit_str, expr, type(exc).__name__, exc + ) + ) + + +def null_debug_action(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + + +class ParserElement(ABC): + """Abstract base level parser element class.""" + + DEFAULT_WHITE_CHARS: str = " \n\t\r" + verbose_stacktrace: bool = False + _literalStringClass: typing.Optional[type] = None + + @staticmethod + def set_default_whitespace_chars(chars: str) -> None: + r""" + Overrides the default whitespace chars + + Example:: + + # default whitespace chars are space, and newline + Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] + + # change to just treat newline as significant + ParserElement.set_default_whitespace_chars(" \t") + Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def'] + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + + # update whitespace all parse expressions defined in this module + for expr in _builtin_exprs: + if expr.copyDefaultWhiteChars: + expr.whiteChars = set(chars) + + @staticmethod + def inline_literals_using(cls: type) -> None: + """ + Set class to be used for inclusion of string literals into a parser. + + Example:: + + # default literal class used is Literal + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + date_str.parse_string("1999/12/31") # -> ['1999', '/', '12', '/', '31'] + + + # change to Suppress + ParserElement.inline_literals_using(Suppress) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + date_str.parse_string("1999/12/31") # -> ['1999', '12', '31'] + """ + ParserElement._literalStringClass = cls + + class DebugActions(NamedTuple): + debug_try: typing.Optional[DebugStartAction] + debug_match: typing.Optional[DebugSuccessAction] + debug_fail: typing.Optional[DebugExceptionAction] + + def __init__(self, savelist: bool = False): + self.parseAction: List[ParseAction] = list() + self.failAction: typing.Optional[ParseFailAction] = None + self.customName = None + self._defaultName = None + self.resultsName = None + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS) + self.copyDefaultWhiteChars = True + # used when checking for left-recursion + self.mayReturnEmpty = False + self.keepTabs = False + self.ignoreExprs: List["ParserElement"] = list() + self.debug = False + self.streamlined = False + # optimize exception handling for subclasses that don't advance parse index + self.mayIndexError = True + self.errmsg = "" + # mark results names as modal (report only last) or cumulative (list all) + self.modalResults = True + # custom debug actions + self.debugActions = self.DebugActions(None, None, None) + # avoid redundant calls to preParse + self.callPreparse = True + self.callDuringTry = False + self.suppress_warnings_: List[Diagnostics] = [] + + def suppress_warning(self, warning_type: Diagnostics) -> "ParserElement": + """ + Suppress warnings emitted for a particular diagnostic on this expression. + + Example:: + + base = pp.Forward() + base.suppress_warning(Diagnostics.warn_on_parse_using_empty_Forward) + + # statement would normally raise a warning, but is now suppressed + print(base.parseString("x")) + + """ + self.suppress_warnings_.append(warning_type) + return self + + def copy(self) -> "ParserElement": + """ + Make a copy of this :class:`ParserElement`. Useful for defining + different parse actions for the same parsing pattern, using copies of + the original parse element. + + Example:: + + integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) + integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K") + integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") + + print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M")) + + prints:: + + [5120, 100, 655360, 268435456] + + Equivalent form of ``expr.copy()`` is just ``expr()``:: + + integerM = integer().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M") + """ + cpy = copy.copy(self) + cpy.parseAction = self.parseAction[:] + cpy.ignoreExprs = self.ignoreExprs[:] + if self.copyDefaultWhiteChars: + cpy.whiteChars = set(ParserElement.DEFAULT_WHITE_CHARS) + return cpy + + def set_results_name( + self, name: str, list_all_matches: bool = False, *, listAllMatches: bool = False + ) -> "ParserElement": + """ + Define name for referencing matching tokens as a nested attribute + of the returned parse results. + + Normally, results names are assigned as you would assign keys in a dict: + any existing value is overwritten by later values. If it is necessary to + keep all values captured for a particular results name, call ``set_results_name`` + with ``list_all_matches`` = True. + + NOTE: ``set_results_name`` returns a *copy* of the original :class:`ParserElement` object; + this is so that the client can define a basic element, such as an + integer, and reference it in multiple places with different names. + + You can also set results names using the abbreviated syntax, + ``expr("name")`` in place of ``expr.set_results_name("name")`` + - see :class:`__call__`. If ``list_all_matches`` is required, use + ``expr("name*")``. + + Example:: + + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + + # equivalent form: + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + """ + listAllMatches = listAllMatches or list_all_matches + return self._setResultsName(name, listAllMatches) + + def _setResultsName(self, name, listAllMatches=False): + if name is None: + return self + newself = self.copy() + if name.endswith("*"): + name = name[:-1] + listAllMatches = True + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def set_break(self, break_flag: bool = True) -> "ParserElement": + """ + Method to invoke the Python pdb debugger when this element is + about to be parsed. Set ``break_flag`` to ``True`` to enable, ``False`` to + disable. + """ + if break_flag: + _parseMethod = self._parse + + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + + # this call to pdb.set_trace() is intentional, not a checkin error + pdb.set_trace() + return _parseMethod(instring, loc, doActions, callPreParse) + + breaker._originalParseMethod = _parseMethod + self._parse = breaker + else: + if hasattr(self._parse, "_originalParseMethod"): + self._parse = self._parse._originalParseMethod + return self + + def set_parse_action(self, *fns: ParseAction, **kwargs) -> "ParserElement": + """ + Define one or more actions to perform when successfully matching parse element definition. + + Parse actions can be called to perform data conversions, do extra validation, + update external data structures, or enhance or replace the parsed tokens. + Each parse action ``fn`` is a callable method with 0-3 arguments, called as + ``fn(s, loc, toks)`` , ``fn(loc, toks)`` , ``fn(toks)`` , or just ``fn()`` , where: + + - s = the original string being parsed (see note below) + - loc = the location of the matching substring + - toks = a list of the matched tokens, packaged as a :class:`ParseResults` object + + The parsed tokens are passed to the parse action as ParseResults. They can be + modified in place using list-style append, extend, and pop operations to update + the parsed list elements; and with dictionary-style item set and del operations + to add, update, or remove any named results. If the tokens are modified in place, + it is not necessary to return them with a return statement. + + Parse actions can also completely replace the given tokens, with another ``ParseResults`` + object, or with some entirely different object (common for parse actions that perform data + conversions). A convenient way to build a new parse result is to define the values + using a dict, and then create the return value using :class:`ParseResults.from_dict`. + + If None is passed as the ``fn`` parse action, all previously added parse actions for this + expression are cleared. + + Optional keyword arguments: + + - call_during_try = (default= ``False``) indicate if parse action should be run during + lookaheads and alternate testing. For parse actions that have side effects, it is + important to only call the parse action once it is determined that it is being + called as part of a successful parse. For parse actions that perform additional + validation, then call_during_try should be passed as True, so that the validation + code is included in the preliminary "try" parses. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See :class:`parse_string` for more + information on parsing strings containing ```` s, and suggested + methods to maintain a consistent view of the parsed string, the parse + location, and line and column positions within the parsed string. + + Example:: + + # parse dates in the form YYYY/MM/DD + + # use parse action to convert toks from str to int at parse time + def convert_to_int(toks): + return int(toks[0]) + + # use a parse action to verify that the date is a valid date + def is_valid_date(instring, loc, toks): + from datetime import date + year, month, day = toks[::2] + try: + date(year, month, day) + except ValueError: + raise ParseException(instring, loc, "invalid date given") + + integer = Word(nums) + date_str = integer + '/' + integer + '/' + integer + + # add parse actions + integer.set_parse_action(convert_to_int) + date_str.set_parse_action(is_valid_date) + + # note that integer fields are now ints, not strings + date_str.run_tests(''' + # successful parse - note that integer fields were converted to ints + 1999/12/31 + + # fail - invalid date + 1999/13/31 + ''') + """ + if list(fns) == [None]: + self.parseAction = [] + else: + if not all(callable(fn) for fn in fns): + raise TypeError("parse actions must be callable") + self.parseAction = [_trim_arity(fn) for fn in fns] + self.callDuringTry = kwargs.get( + "call_during_try", kwargs.get("callDuringTry", False) + ) + return self + + def add_parse_action(self, *fns: ParseAction, **kwargs) -> "ParserElement": + """ + Add one or more parse actions to expression's list of parse actions. See :class:`set_parse_action`. + + See examples in :class:`copy`. + """ + self.parseAction += [_trim_arity(fn) for fn in fns] + self.callDuringTry = self.callDuringTry or kwargs.get( + "call_during_try", kwargs.get("callDuringTry", False) + ) + return self + + def add_condition(self, *fns: ParseCondition, **kwargs) -> "ParserElement": + """Add a boolean predicate function to expression's list of parse actions. See + :class:`set_parse_action` for function call signatures. Unlike ``set_parse_action``, + functions passed to ``add_condition`` need to return boolean success/fail of the condition. + + Optional keyword arguments: + + - message = define a custom message to be used in the raised exception + - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise + ParseException + - call_during_try = boolean to indicate if this method should be called during internal tryParse calls, + default=False + + Example:: + + integer = Word(nums).set_parse_action(lambda toks: int(toks[0])) + year_int = integer.copy() + year_int.add_condition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") + date_str = year_int + '/' + integer + '/' + integer + + result = date_str.parse_string("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), + (line:1, col:1) + """ + for fn in fns: + self.parseAction.append( + condition_as_parse_action( + fn, message=kwargs.get("message"), fatal=kwargs.get("fatal", False) + ) + ) + + self.callDuringTry = self.callDuringTry or kwargs.get( + "call_during_try", kwargs.get("callDuringTry", False) + ) + return self + + def set_fail_action(self, fn: ParseFailAction) -> "ParserElement": + """ + Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + ``fn(s, loc, expr, err)`` where: + + - s = string being parsed + - loc = location where expression match was attempted and failed + - expr = the parse expression that failed + - err = the exception thrown + + The function returns no value. It may throw :class:`ParseFatalException` + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables(self, instring, loc): + exprsFound = True + while exprsFound: + exprsFound = False + for e in self.ignoreExprs: + try: + while 1: + loc, dummy = e._parse(instring, loc) + exprsFound = True + except ParseException: + pass + return loc + + def preParse(self, instring, loc): + if self.ignoreExprs: + loc = self._skipIgnorables(instring, loc) + + if self.skipWhitespace: + instrlen = len(instring) + white_chars = self.whiteChars + while loc < instrlen and instring[loc] in white_chars: + loc += 1 + + return loc + + def parseImpl(self, instring, loc, doActions=True): + return loc, [] + + def postParse(self, instring, loc, tokenlist): + return tokenlist + + # @profile + def _parseNoCache( + self, instring, loc, doActions=True, callPreParse=True + ) -> Tuple[int, ParseResults]: + TRY, MATCH, FAIL = 0, 1, 2 + debugging = self.debug # and doActions) + len_instring = len(instring) + + if debugging or self.failAction: + # print("Match {} at loc {}({}, {})".format(self, loc, lineno(loc, instring), col(loc, instring))) + try: + if callPreParse and self.callPreparse: + pre_loc = self.preParse(instring, loc) + else: + pre_loc = loc + tokens_start = pre_loc + if self.debugActions.debug_try: + self.debugActions.debug_try(instring, tokens_start, self, False) + if self.mayIndexError or pre_loc >= len_instring: + try: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + except IndexError: + raise ParseException(instring, len_instring, self.errmsg, self) + else: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + except Exception as err: + # print("Exception raised:", err) + if self.debugActions.debug_fail: + self.debugActions.debug_fail( + instring, tokens_start, self, err, False + ) + if self.failAction: + self.failAction(instring, tokens_start, self, err) + raise + else: + if callPreParse and self.callPreparse: + pre_loc = self.preParse(instring, loc) + else: + pre_loc = loc + tokens_start = pre_loc + if self.mayIndexError or pre_loc >= len_instring: + try: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + except IndexError: + raise ParseException(instring, len_instring, self.errmsg, self) + else: + loc, tokens = self.parseImpl(instring, pre_loc, doActions) + + tokens = self.postParse(instring, loc, tokens) + + ret_tokens = ParseResults( + tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults + ) + if self.parseAction and (doActions or self.callDuringTry): + if debugging: + try: + for fn in self.parseAction: + try: + tokens = fn(instring, tokens_start, ret_tokens) + except IndexError as parse_action_exc: + exc = ParseException("exception raised in parse action") + raise exc from parse_action_exc + + if tokens is not None and tokens is not ret_tokens: + ret_tokens = ParseResults( + tokens, + self.resultsName, + asList=self.saveAsList + and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults, + ) + except Exception as err: + # print "Exception raised in user parse action:", err + if self.debugActions.debug_fail: + self.debugActions.debug_fail( + instring, tokens_start, self, err, False + ) + raise + else: + for fn in self.parseAction: + try: + tokens = fn(instring, tokens_start, ret_tokens) + except IndexError as parse_action_exc: + exc = ParseException("exception raised in parse action") + raise exc from parse_action_exc + + if tokens is not None and tokens is not ret_tokens: + ret_tokens = ParseResults( + tokens, + self.resultsName, + asList=self.saveAsList + and isinstance(tokens, (ParseResults, list)), + modal=self.modalResults, + ) + if debugging: + # print("Matched", self, "->", ret_tokens.as_list()) + if self.debugActions.debug_match: + self.debugActions.debug_match( + instring, tokens_start, loc, self, ret_tokens, False + ) + + return loc, ret_tokens + + def try_parse(self, instring: str, loc: int, raise_fatal: bool = False) -> int: + try: + return self._parse(instring, loc, doActions=False)[0] + except ParseFatalException: + if raise_fatal: + raise + raise ParseException(instring, loc, self.errmsg, self) + + def can_parse_next(self, instring: str, loc: int) -> bool: + try: + self.try_parse(instring, loc) + except (ParseException, IndexError): + return False + else: + return True + + # cache for left-recursion in Forward references + recursion_lock = RLock() + recursion_memos: typing.Dict[ + Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]] + ] = {} + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + packrat_cache = ( + {} + ) # this is set later by enabled_packrat(); this is here so that reset_cache() doesn't fail + packrat_cache_lock = RLock() + packrat_cache_stats = [0, 0] + + # this method gets repeatedly called during backtracking with the same arguments - + # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression + def _parseCache( + self, instring, loc, doActions=True, callPreParse=True + ) -> Tuple[int, ParseResults]: + HIT, MISS = 0, 1 + TRY, MATCH, FAIL = 0, 1, 2 + lookup = (self, instring, loc, callPreParse, doActions) + with ParserElement.packrat_cache_lock: + cache = ParserElement.packrat_cache + value = cache.get(lookup) + if value is cache.not_in_cache: + ParserElement.packrat_cache_stats[MISS] += 1 + try: + value = self._parseNoCache(instring, loc, doActions, callPreParse) + except ParseBaseException as pe: + # cache a copy of the exception, without the traceback + cache.set(lookup, pe.__class__(*pe.args)) + raise + else: + cache.set(lookup, (value[0], value[1].copy(), loc)) + return value + else: + ParserElement.packrat_cache_stats[HIT] += 1 + if self.debug and self.debugActions.debug_try: + try: + self.debugActions.debug_try(instring, loc, self, cache_hit=True) + except TypeError: + pass + if isinstance(value, Exception): + if self.debug and self.debugActions.debug_fail: + try: + self.debugActions.debug_fail( + instring, loc, self, value, cache_hit=True + ) + except TypeError: + pass + raise value + + loc_, result, endloc = value[0], value[1].copy(), value[2] + if self.debug and self.debugActions.debug_match: + try: + self.debugActions.debug_match( + instring, loc_, endloc, self, result, cache_hit=True + ) + except TypeError: + pass + + return loc_, result + + _parse = _parseNoCache + + @staticmethod + def reset_cache() -> None: + ParserElement.packrat_cache.clear() + ParserElement.packrat_cache_stats[:] = [0] * len( + ParserElement.packrat_cache_stats + ) + ParserElement.recursion_memos.clear() + + _packratEnabled = False + _left_recursion_enabled = False + + @staticmethod + def disable_memoization() -> None: + """ + Disables active Packrat or Left Recursion parsing and their memoization + + This method also works if neither Packrat nor Left Recursion are enabled. + This makes it safe to call before activating Packrat nor Left Recursion + to clear any previous settings. + """ + ParserElement.reset_cache() + ParserElement._left_recursion_enabled = False + ParserElement._packratEnabled = False + ParserElement._parse = ParserElement._parseNoCache + + @staticmethod + def enable_left_recursion( + cache_size_limit: typing.Optional[int] = None, *, force=False + ) -> None: + """ + Enables "bounded recursion" parsing, which allows for both direct and indirect + left-recursion. During parsing, left-recursive :class:`Forward` elements are + repeatedly matched with a fixed recursion depth that is gradually increased + until finding the longest match. + + Example:: + + import pyparsing as pp + pp.ParserElement.enable_left_recursion() + + E = pp.Forward("E") + num = pp.Word(pp.nums) + # match `num`, or `num '+' num`, or `num '+' num '+' num`, ... + E <<= E + '+' - num | num + + print(E.parse_string("1+2+3")) + + Recursion search naturally memoizes matches of ``Forward`` elements and may + thus skip reevaluation of parse actions during backtracking. This may break + programs with parse actions which rely on strict ordering of side-effects. + + Parameters: + + - cache_size_limit - (default=``None``) - memoize at most this many + ``Forward`` elements during matching; if ``None`` (the default), + memoize all ``Forward`` elements. + + Bounded Recursion parsing works similar but not identical to Packrat parsing, + thus the two cannot be used together. Use ``force=True`` to disable any + previous, conflicting settings. + """ + if force: + ParserElement.disable_memoization() + elif ParserElement._packratEnabled: + raise RuntimeError("Packrat and Bounded Recursion are not compatible") + if cache_size_limit is None: + ParserElement.recursion_memos = _UnboundedMemo() + elif cache_size_limit > 0: + ParserElement.recursion_memos = _LRUMemo(capacity=cache_size_limit) + else: + raise NotImplementedError("Memo size of %s" % cache_size_limit) + ParserElement._left_recursion_enabled = True + + @staticmethod + def enable_packrat(cache_size_limit: int = 128, *, force: bool = False) -> None: + """ + Enables "packrat" parsing, which adds memoizing to the parsing logic. + Repeated parse attempts at the same string location (which happens + often in many complex grammars) can immediately return a cached value, + instead of re-executing parsing/validating code. Memoizing is done of + both valid results and parsing exceptions. + + Parameters: + + - cache_size_limit - (default= ``128``) - if an integer value is provided + will limit the size of the packrat cache; if None is passed, then + the cache size will be unbounded; if 0 is passed, the cache will + be effectively disabled. + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing. To activate the packrat feature, your + program must call the class method :class:`ParserElement.enable_packrat`. + For best results, call ``enable_packrat()`` immediately after + importing pyparsing. + + Example:: + + import pyparsing + pyparsing.ParserElement.enable_packrat() + + Packrat parsing works similar but not identical to Bounded Recursion parsing, + thus the two cannot be used together. Use ``force=True`` to disable any + previous, conflicting settings. + """ + if force: + ParserElement.disable_memoization() + elif ParserElement._left_recursion_enabled: + raise RuntimeError("Packrat and Bounded Recursion are not compatible") + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + if cache_size_limit is None: + ParserElement.packrat_cache = _UnboundedCache() + else: + ParserElement.packrat_cache = _FifoCache(cache_size_limit) + ParserElement._parse = ParserElement._parseCache + + def parse_string( + self, instring: str, parse_all: bool = False, *, parseAll: bool = False + ) -> ParseResults: + """ + Parse a string with respect to the parser definition. This function is intended as the primary interface to the + client code. + + :param instring: The input string to be parsed. + :param parse_all: If set, the entire input string must match the grammar. + :param parseAll: retained for pre-PEP8 compatibility, will be removed in a future release. + :raises ParseException: Raised if ``parse_all`` is set and the input string does not match the whole grammar. + :returns: the parsed data as a :class:`ParseResults` object, which may be accessed as a `list`, a `dict`, or + an object with attributes if the given parser includes results names. + + If the input string is required to match the entire grammar, ``parse_all`` flag must be set to ``True``. This + is also equivalent to ending the grammar with :class:`StringEnd`(). + + To report proper column numbers, ``parse_string`` operates on a copy of the input string where all tabs are + converted to spaces (8 spaces per tab, as per the default in ``string.expandtabs``). If the input string + contains tabs and the grammar uses parse actions that use the ``loc`` argument to index into the string + being parsed, one can ensure a consistent view of the input string by doing one of the following: + + - calling ``parse_with_tabs`` on your grammar before calling ``parse_string`` (see :class:`parse_with_tabs`), + - define your parse action using the full ``(s,loc,toks)`` signature, and reference the input string using the + parse action's ``s`` argument, or + - explicitly expand the tabs in your input string before calling ``parse_string``. + + Examples: + + By default, partial matches are OK. + + >>> res = Word('a').parse_string('aaaaabaaa') + >>> print(res) + ['aaaaa'] + + The parsing behavior varies by the inheriting class of this abstract class. Please refer to the children + directly to see more examples. + + It raises an exception if parse_all flag is set and instring does not match the whole grammar. + + >>> res = Word('a').parse_string('aaaaabaaa', parse_all=True) + Traceback (most recent call last): + ... + pyparsing.ParseException: Expected end of text, found 'b' (at char 5), (line:1, col:6) + """ + parseAll = parse_all or parseAll + + ParserElement.reset_cache() + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + if not self.keepTabs: + instring = instring.expandtabs() + try: + loc, tokens = self._parse(instring, 0) + if parseAll: + loc = self.preParse(instring, loc) + se = Empty() + StringEnd() + se._parse(instring, loc) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clearing out pyparsing internal stack trace + raise exc.with_traceback(None) + else: + return tokens + + def scan_string( + self, + instring: str, + max_matches: int = _MAX_INT, + overlap: bool = False, + *, + debug: bool = False, + maxMatches: int = _MAX_INT, + ) -> Generator[Tuple[ParseResults, int, int], None, None]: + """ + Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + ``max_matches`` argument, to clip scanning after 'n' matches are found. If + ``overlap`` is specified, then overlapping matches will be reported. + + Note that the start and end locations are reported relative to the string + being parsed. See :class:`parse_string` for more information on parsing + strings with embedded tabs. + + Example:: + + source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" + print(source) + for tokens, start, end in Word(alphas).scan_string(source): + print(' '*start + '^'*(end-start)) + print(' '*start + tokens[0]) + + prints:: + + sldjf123lsdjjkf345sldkjf879lkjsfd987 + ^^^^^ + sldjf + ^^^^^^^ + lsdjjkf + ^^^^^^ + sldkjf + ^^^^^^ + lkjsfd + """ + maxMatches = min(maxMatches, max_matches) + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = str(instring).expandtabs() + instrlen = len(instring) + loc = 0 + preparseFn = self.preParse + parseFn = self._parse + ParserElement.resetCache() + matches = 0 + try: + while loc <= instrlen and matches < maxMatches: + try: + preloc = preparseFn(instring, loc) + nextLoc, tokens = parseFn(instring, preloc, callPreParse=False) + except ParseException: + loc = preloc + 1 + else: + if nextLoc > loc: + matches += 1 + if debug: + print( + { + "tokens": tokens.asList(), + "start": preloc, + "end": nextLoc, + } + ) + yield tokens, preloc, nextLoc + if overlap: + nextloc = preparseFn(instring, loc) + if nextloc > loc: + loc = nextLoc + else: + loc += 1 + else: + loc = nextLoc + else: + loc = preloc + 1 + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc.with_traceback(None) + + def transform_string(self, instring: str, *, debug: bool = False) -> str: + """ + Extension to :class:`scan_string`, to modify matching text with modified tokens that may + be returned from a parse action. To use ``transform_string``, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking ``transform_string()`` on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. ``transform_string()`` returns the resulting transformed string. + + Example:: + + wd = Word(alphas) + wd.set_parse_action(lambda toks: toks[0].title()) + + print(wd.transform_string("now is the winter of our discontent made glorious summer by this sun of york.")) + + prints:: + + Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York. + """ + out: List[str] = [] + lastE = 0 + # force preservation of s, to minimize unwanted transformation of string, and to + # keep string locs straight between transform_string and scan_string + self.keepTabs = True + try: + for t, s, e in self.scan_string(instring, debug=debug): + out.append(instring[lastE:s]) + if t: + if isinstance(t, ParseResults): + out += t.as_list() + elif isinstance(t, Iterable) and not isinstance(t, str_type): + out.extend(t) + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + out = [o for o in out if o] + return "".join([str(s) for s in _flatten(out)]) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc.with_traceback(None) + + def search_string( + self, + instring: str, + max_matches: int = _MAX_INT, + *, + debug: bool = False, + maxMatches: int = _MAX_INT, + ) -> ParseResults: + """ + Another extension to :class:`scan_string`, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + ``max_matches`` argument, to clip searching after 'n' matches are found. + + Example:: + + # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters + cap_word = Word(alphas.upper(), alphas.lower()) + + print(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity")) + + # the sum() builtin can be used to merge results into a single ParseResults object + print(sum(cap_word.search_string("More than Iron, more than Lead, more than Gold I need Electricity"))) + + prints:: + + [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] + ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] + """ + maxMatches = min(maxMatches, max_matches) + try: + return ParseResults( + [t for t, s, e in self.scan_string(instring, maxMatches, debug=debug)] + ) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc.with_traceback(None) + + def split( + self, + instring: str, + maxsplit: int = _MAX_INT, + include_separators: bool = False, + *, + includeSeparators=False, + ) -> Generator[str, None, None]: + """ + Generator method to split a string using the given expression as a separator. + May be called with optional ``maxsplit`` argument, to limit the number of splits; + and the optional ``include_separators`` argument (default= ``False``), if the separating + matching text should be included in the split results. + + Example:: + + punc = one_of(list(".,;:/-!?")) + print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) + + prints:: + + ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] + """ + includeSeparators = includeSeparators or include_separators + last = 0 + for t, s, e in self.scan_string(instring, max_matches=maxsplit): + yield instring[last:s] + if includeSeparators: + yield t[0] + last = e + yield instring[last:] + + def __add__(self, other) -> "ParserElement": + """ + Implementation of ``+`` operator - returns :class:`And`. Adding strings to a :class:`ParserElement` + converts them to :class:`Literal`s by default. + + Example:: + + greet = Word(alphas) + "," + Word(alphas) + "!" + hello = "Hello, World!" + print(hello, "->", greet.parse_string(hello)) + + prints:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + + ``...`` may be used as a parse expression as a short form of :class:`SkipTo`. + + Literal('start') + ... + Literal('end') + + is equivalent to: + + Literal('start') + SkipTo('end')("_skipped*") + Literal('end') + + Note that the skipped text is returned with '_skipped' as a results name, + and to support having multiple skips in the same parser, the value returned is + a list of all skipped text. + """ + if other is Ellipsis: + return _PendingSkip(self) + + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return And([self, other]) + + def __radd__(self, other) -> "ParserElement": + """ + Implementation of ``+`` operator when left operand is not a :class:`ParserElement` + """ + if other is Ellipsis: + return SkipTo(self)("_skipped*") + self + + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return other + self + + def __sub__(self, other) -> "ParserElement": + """ + Implementation of ``-`` operator, returns :class:`And` with error stop + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return self + And._ErrorStop() + other + + def __rsub__(self, other) -> "ParserElement": + """ + Implementation of ``-`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return other - self + + def __mul__(self, other) -> "ParserElement": + """ + Implementation of ``*`` operator, allows use of ``expr * 3`` in place of + ``expr + expr + expr``. Expressions may also be multiplied by a 2-integer + tuple, similar to ``{min, max}`` multipliers in regular expressions. Tuples + may also include ``None`` as in: + - ``expr*(n, None)`` or ``expr*(n, )`` is equivalent + to ``expr*n + ZeroOrMore(expr)`` + (read as "at least n instances of ``expr``") + - ``expr*(None, n)`` is equivalent to ``expr*(0, n)`` + (read as "0 to n instances of ``expr``") + - ``expr*(None, None)`` is equivalent to ``ZeroOrMore(expr)`` + - ``expr*(1, None)`` is equivalent to ``OneOrMore(expr)`` + + Note that ``expr*(None, n)`` does not raise an exception if + more than n exprs exist in the input stream; that is, + ``expr*(None, n)`` does not enforce a maximum number of expr + occurrences. If this behavior is desired, then write + ``expr*(None, n) + ~expr`` + """ + if other is Ellipsis: + other = (0, None) + elif isinstance(other, tuple) and other[:1] == (Ellipsis,): + other = ((0,) + other[1:] + (None,))[:2] + + if isinstance(other, int): + minElements, optElements = other, 0 + elif isinstance(other, tuple): + other = tuple(o if o is not Ellipsis else None for o in other) + other = (other + (None, None))[:2] + if other[0] is None: + other = (0, other[1]) + if isinstance(other[0], int) and other[1] is None: + if other[0] == 0: + return ZeroOrMore(self) + if other[0] == 1: + return OneOrMore(self) + else: + return self * other[0] + ZeroOrMore(self) + elif isinstance(other[0], int) and isinstance(other[1], int): + minElements, optElements = other + optElements -= minElements + else: + raise TypeError( + "cannot multiply ParserElement and ({}) objects".format( + ",".join(type(item).__name__ for item in other) + ) + ) + else: + raise TypeError( + "cannot multiply ParserElement and {} objects".format( + type(other).__name__ + ) + ) + + if minElements < 0: + raise ValueError("cannot multiply ParserElement by negative value") + if optElements < 0: + raise ValueError( + "second tuple value must be greater or equal to first tuple value" + ) + if minElements == optElements == 0: + return And([]) + + if optElements: + + def makeOptionalList(n): + if n > 1: + return Opt(self + makeOptionalList(n - 1)) + else: + return Opt(self) + + if minElements: + if minElements == 1: + ret = self + makeOptionalList(optElements) + else: + ret = And([self] * minElements) + makeOptionalList(optElements) + else: + ret = makeOptionalList(optElements) + else: + if minElements == 1: + ret = self + else: + ret = And([self] * minElements) + return ret + + def __rmul__(self, other) -> "ParserElement": + return self.__mul__(other) + + def __or__(self, other) -> "ParserElement": + """ + Implementation of ``|`` operator - returns :class:`MatchFirst` + """ + if other is Ellipsis: + return _PendingSkip(self, must_skip=True) + + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return MatchFirst([self, other]) + + def __ror__(self, other) -> "ParserElement": + """ + Implementation of ``|`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return other | self + + def __xor__(self, other) -> "ParserElement": + """ + Implementation of ``^`` operator - returns :class:`Or` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return Or([self, other]) + + def __rxor__(self, other) -> "ParserElement": + """ + Implementation of ``^`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return other ^ self + + def __and__(self, other) -> "ParserElement": + """ + Implementation of ``&`` operator - returns :class:`Each` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return Each([self, other]) + + def __rand__(self, other) -> "ParserElement": + """ + Implementation of ``&`` operator when left operand is not a :class:`ParserElement` + """ + if isinstance(other, str_type): + other = self._literalStringClass(other) + if not isinstance(other, ParserElement): + raise TypeError( + "Cannot combine element of type {} with ParserElement".format( + type(other).__name__ + ) + ) + return other & self + + def __invert__(self) -> "ParserElement": + """ + Implementation of ``~`` operator - returns :class:`NotAny` + """ + return NotAny(self) + + # disable __iter__ to override legacy use of sequential access to __getitem__ to + # iterate over a sequence + __iter__ = None + + def __getitem__(self, key): + """ + use ``[]`` indexing notation as a short form for expression repetition: + + - ``expr[n]`` is equivalent to ``expr*n`` + - ``expr[m, n]`` is equivalent to ``expr*(m, n)`` + - ``expr[n, ...]`` or ``expr[n,]`` is equivalent + to ``expr*n + ZeroOrMore(expr)`` + (read as "at least n instances of ``expr``") + - ``expr[..., n]`` is equivalent to ``expr*(0, n)`` + (read as "0 to n instances of ``expr``") + - ``expr[...]`` and ``expr[0, ...]`` are equivalent to ``ZeroOrMore(expr)`` + - ``expr[1, ...]`` is equivalent to ``OneOrMore(expr)`` + + ``None`` may be used in place of ``...``. + + Note that ``expr[..., n]`` and ``expr[m, n]``do not raise an exception + if more than ``n`` ``expr``s exist in the input stream. If this behavior is + desired, then write ``expr[..., n] + ~expr``. + """ + + # convert single arg keys to tuples + try: + if isinstance(key, str_type): + key = (key,) + iter(key) + except TypeError: + key = (key, key) + + if len(key) > 2: + raise TypeError( + "only 1 or 2 index arguments supported ({}{})".format( + key[:5], "... [{}]".format(len(key)) if len(key) > 5 else "" + ) + ) + + # clip to 2 elements + ret = self * tuple(key[:2]) + return ret + + def __call__(self, name: str = None) -> "ParserElement": + """ + Shortcut for :class:`set_results_name`, with ``list_all_matches=False``. + + If ``name`` is given with a trailing ``'*'`` character, then ``list_all_matches`` will be + passed as ``True``. + + If ``name` is omitted, same as calling :class:`copy`. + + Example:: + + # these are equivalent + userdata = Word(alphas).set_results_name("name") + Word(nums + "-").set_results_name("socsecno") + userdata = Word(alphas)("name") + Word(nums + "-")("socsecno") + """ + if name is not None: + return self._setResultsName(name) + else: + return self.copy() + + def suppress(self) -> "ParserElement": + """ + Suppresses the output of this :class:`ParserElement`; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress(self) + + def ignore_whitespace(self, recursive: bool = True) -> "ParserElement": + """ + Enables the skipping of whitespace before matching the characters in the + :class:`ParserElement`'s defined pattern. + + :param recursive: If ``True`` (the default), also enable whitespace skipping in child elements (if any) + """ + self.skipWhitespace = True + return self + + def leave_whitespace(self, recursive: bool = True) -> "ParserElement": + """ + Disables the skipping of whitespace before matching the characters in the + :class:`ParserElement`'s defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + + :param recursive: If true (the default), also disable whitespace skipping in child elements (if any) + """ + self.skipWhitespace = False + return self + + def set_whitespace_chars( + self, chars: Union[Set[str], str], copy_defaults: bool = False + ) -> "ParserElement": + """ + Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = set(chars) + self.copyDefaultWhiteChars = copy_defaults + return self + + def parse_with_tabs(self) -> "ParserElement": + """ + Overrides default behavior to expand ```` s to spaces before parsing the input string. + Must be called before ``parse_string`` when the input grammar contains elements that + match ```` characters. + """ + self.keepTabs = True + return self + + def ignore(self, other: "ParserElement") -> "ParserElement": + """ + Define expression to be ignored (e.g., comments) while doing pattern + matching; may be called repeatedly, to define multiple comment or other + ignorable patterns. + + Example:: + + patt = Word(alphas)[1, ...] + patt.parse_string('ablaj /* comment */ lskjd') + # -> ['ablaj'] + + patt.ignore(c_style_comment) + patt.parse_string('ablaj /* comment */ lskjd') + # -> ['ablaj', 'lskjd'] + """ + import typing + + if isinstance(other, str_type): + other = Suppress(other) + + if isinstance(other, Suppress): + if other not in self.ignoreExprs: + self.ignoreExprs.append(other) + else: + self.ignoreExprs.append(Suppress(other.copy())) + return self + + def set_debug_actions( + self, + start_action: DebugStartAction, + success_action: DebugSuccessAction, + exception_action: DebugExceptionAction, + ) -> "ParserElement": + """ + Customize display of debugging messages while doing pattern matching: + + - ``start_action`` - method to be called when an expression is about to be parsed; + should have the signature ``fn(input_string: str, location: int, expression: ParserElement, cache_hit: bool)`` + + - ``success_action`` - method to be called when an expression has successfully parsed; + should have the signature ``fn(input_string: str, start_location: int, end_location: int, expression: ParserELement, parsed_tokens: ParseResults, cache_hit: bool)`` + + - ``exception_action`` - method to be called when expression fails to parse; + should have the signature ``fn(input_string: str, location: int, expression: ParserElement, exception: Exception, cache_hit: bool)`` + """ + self.debugActions = self.DebugActions( + start_action or _default_start_debug_action, + success_action or _default_success_debug_action, + exception_action or _default_exception_debug_action, + ) + self.debug = True + return self + + def set_debug(self, flag: bool = True) -> "ParserElement": + """ + Enable display of debugging messages while doing pattern matching. + Set ``flag`` to ``True`` to enable, ``False`` to disable. + + Example:: + + wd = Word(alphas).set_name("alphaword") + integer = Word(nums).set_name("numword") + term = wd | integer + + # turn on debugging for wd + wd.set_debug() + + term[1, ...].parse_string("abc 123 xyz 890") + + prints:: + + Match alphaword at loc 0(1,1) + Matched alphaword -> ['abc'] + Match alphaword at loc 3(1,4) + Exception raised:Expected alphaword (at char 4), (line:1, col:5) + Match alphaword at loc 7(1,8) + Matched alphaword -> ['xyz'] + Match alphaword at loc 11(1,12) + Exception raised:Expected alphaword (at char 12), (line:1, col:13) + Match alphaword at loc 15(1,16) + Exception raised:Expected alphaword (at char 15), (line:1, col:16) + + The output shown is that produced by the default debug actions - custom debug actions can be + specified using :class:`set_debug_actions`. Prior to attempting + to match the ``wd`` expression, the debugging message ``"Match at loc (,)"`` + is shown. Then if the parse succeeds, a ``"Matched"`` message is shown, or an ``"Exception raised"`` + message is shown. Also note the use of :class:`set_name` to assign a human-readable name to the expression, + which makes debugging and exception messages easier to understand - for instance, the default + name created for the :class:`Word` expression without calling ``set_name`` is ``"W:(A-Za-z)"``. + """ + if flag: + self.set_debug_actions( + _default_start_debug_action, + _default_success_debug_action, + _default_exception_debug_action, + ) + else: + self.debug = False + return self + + @property + def default_name(self) -> str: + if self._defaultName is None: + self._defaultName = self._generateDefaultName() + return self._defaultName + + @abstractmethod + def _generateDefaultName(self): + """ + Child classes must define this method, which defines how the ``default_name`` is set. + """ + + def set_name(self, name: str) -> "ParserElement": + """ + Define name for this expression, makes debugging and exception messages clearer. + Example:: + Word(nums).parse_string("ABC") # -> Exception: Expected W:(0-9) (at char 0), (line:1, col:1) + Word(nums).set_name("integer").parse_string("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) + """ + self.customName = name + self.errmsg = "Expected " + self.name + if __diag__.enable_debug_on_named_expressions: + self.set_debug() + return self + + @property + def name(self) -> str: + # This will use a user-defined name if available, but otherwise defaults back to the auto-generated name + return self.customName if self.customName is not None else self.default_name + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return str(self) + + def streamline(self) -> "ParserElement": + self.streamlined = True + self._defaultName = None + return self + + def recurse(self) -> Sequence["ParserElement"]: + return [] + + def _checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] + for e in self.recurse(): + e._checkRecursion(subRecCheckList) + + def validate(self, validateTrace=None) -> None: + """ + Check defined expressions for valid structure, check for infinite recursive definitions. + """ + self._checkRecursion([]) + + def parse_file( + self, + file_or_filename: Union[str, Path, TextIO], + encoding: str = "utf-8", + parse_all: bool = False, + *, + parseAll: bool = False, + ) -> ParseResults: + """ + Execute the parse expression on the given file or filename. + If a filename is specified (instead of a file object), + the entire file is opened, read, and closed before parsing. + """ + parseAll = parseAll or parse_all + try: + file_contents = file_or_filename.read() + except AttributeError: + with open(file_or_filename, "r", encoding=encoding) as f: + file_contents = f.read() + try: + return self.parse_string(file_contents, parseAll) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc.with_traceback(None) + + def __eq__(self, other): + if self is other: + return True + elif isinstance(other, str_type): + return self.matches(other, parse_all=True) + elif isinstance(other, ParserElement): + return vars(self) == vars(other) + return False + + def __hash__(self): + return id(self) + + def matches( + self, test_string: str, parse_all: bool = True, *, parseAll: bool = True + ) -> bool: + """ + Method for quick testing of a parser against a test string. Good for simple + inline microtests of sub expressions while building up larger parser. + + Parameters: + - ``test_string`` - to test against this expression for a match + - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests + + Example:: + + expr = Word(nums) + assert expr.matches("100") + """ + parseAll = parseAll and parse_all + try: + self.parse_string(str(test_string), parse_all=parseAll) + return True + except ParseBaseException: + return False + + def run_tests( + self, + tests: Union[str, List[str]], + parse_all: bool = True, + comment: typing.Optional[Union["ParserElement", str]] = "#", + full_dump: bool = True, + print_results: bool = True, + failure_tests: bool = False, + post_parse: Callable[[str, ParseResults], str] = None, + file: typing.Optional[TextIO] = None, + with_line_numbers: bool = False, + *, + parseAll: bool = True, + fullDump: bool = True, + printResults: bool = True, + failureTests: bool = False, + postParse: Callable[[str, ParseResults], str] = None, + ) -> Tuple[bool, List[Tuple[str, Union[ParseResults, Exception]]]]: + """ + Execute the parse expression on a series of test strings, showing each + test, the parsed results or where the parse failed. Quick and easy way to + run a parse expression against a list of sample strings. + + Parameters: + - ``tests`` - a list of separate test strings, or a multiline string of test strings + - ``parse_all`` - (default= ``True``) - flag to pass to :class:`parse_string` when running tests + - ``comment`` - (default= ``'#'``) - expression for indicating embedded comments in the test + string; pass None to disable comment filtering + - ``full_dump`` - (default= ``True``) - dump results as list followed by results names in nested outline; + if False, only dump nested list + - ``print_results`` - (default= ``True``) prints test output to stdout + - ``failure_tests`` - (default= ``False``) indicates if these tests are expected to fail parsing + - ``post_parse`` - (default= ``None``) optional callback for successful parse results; called as + `fn(test_string, parse_results)` and returns a string to be added to the test output + - ``file`` - (default= ``None``) optional file-like object to which test output will be written; + if None, will default to ``sys.stdout`` + - ``with_line_numbers`` - default= ``False``) show test strings with line and column numbers + + Returns: a (success, results) tuple, where success indicates that all tests succeeded + (or failed if ``failure_tests`` is True), and the results contain a list of lines of each + test's output + + Example:: + + number_expr = pyparsing_common.number.copy() + + result = number_expr.run_tests(''' + # unsigned integer + 100 + # negative integer + -100 + # float with scientific notation + 6.02e23 + # integer with scientific notation + 1e-12 + ''') + print("Success" if result[0] else "Failed!") + + result = number_expr.run_tests(''' + # stray character + 100Z + # missing leading digit before '.' + -.100 + # too many '.' + 3.14.159 + ''', failure_tests=True) + print("Success" if result[0] else "Failed!") + + prints:: + + # unsigned integer + 100 + [100] + + # negative integer + -100 + [-100] + + # float with scientific notation + 6.02e23 + [6.02e+23] + + # integer with scientific notation + 1e-12 + [1e-12] + + Success + + # stray character + 100Z + ^ + FAIL: Expected end of text (at char 3), (line:1, col:4) + + # missing leading digit before '.' + -.100 + ^ + FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1) + + # too many '.' + 3.14.159 + ^ + FAIL: Expected end of text (at char 4), (line:1, col:5) + + Success + + Each test string must be on a single line. If you want to test a string that spans multiple + lines, create a test like this:: + + expr.run_tests(r"this is a test\\n of strings that spans \\n 3 lines") + + (Note that this is a raw string literal, you must include the leading ``'r'``.) + """ + from .testing import pyparsing_test + + parseAll = parseAll and parse_all + fullDump = fullDump and full_dump + printResults = printResults and print_results + failureTests = failureTests or failure_tests + postParse = postParse or post_parse + if isinstance(tests, str_type): + line_strip = type(tests).strip + tests = [line_strip(test_line) for test_line in tests.rstrip().splitlines()] + if isinstance(comment, str_type): + comment = Literal(comment) + if file is None: + file = sys.stdout + print_ = file.write + + result: Union[ParseResults, Exception] + allResults = [] + comments = [] + success = True + NL = Literal(r"\n").add_parse_action(replace_with("\n")).ignore(quoted_string) + BOM = "\ufeff" + for t in tests: + if comment is not None and comment.matches(t, False) or comments and not t: + comments.append( + pyparsing_test.with_line_numbers(t) if with_line_numbers else t + ) + continue + if not t: + continue + out = [ + "\n" + "\n".join(comments) if comments else "", + pyparsing_test.with_line_numbers(t) if with_line_numbers else t, + ] + comments = [] + try: + # convert newline marks to actual newlines, and strip leading BOM if present + t = NL.transform_string(t.lstrip(BOM)) + result = self.parse_string(t, parse_all=parseAll) + except ParseBaseException as pe: + fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" + out.append(pe.explain()) + out.append("FAIL: " + str(pe)) + if ParserElement.verbose_stacktrace: + out.extend(traceback.format_tb(pe.__traceback__)) + success = success and failureTests + result = pe + except Exception as exc: + out.append("FAIL-EXCEPTION: {}: {}".format(type(exc).__name__, exc)) + if ParserElement.verbose_stacktrace: + out.extend(traceback.format_tb(exc.__traceback__)) + success = success and failureTests + result = exc + else: + success = success and not failureTests + if postParse is not None: + try: + pp_value = postParse(t, result) + if pp_value is not None: + if isinstance(pp_value, ParseResults): + out.append(pp_value.dump()) + else: + out.append(str(pp_value)) + else: + out.append(result.dump()) + except Exception as e: + out.append(result.dump(full=fullDump)) + out.append( + "{} failed: {}: {}".format( + postParse.__name__, type(e).__name__, e + ) + ) + else: + out.append(result.dump(full=fullDump)) + out.append("") + + if printResults: + print_("\n".join(out)) + + allResults.append((t, result)) + + return success, allResults + + def create_diagram( + self, + output_html: Union[TextIO, Path, str], + vertical: int = 3, + show_results_names: bool = False, + show_groups: bool = False, + **kwargs, + ) -> None: + """ + Create a railroad diagram for the parser. + + Parameters: + - output_html (str or file-like object) - output target for generated + diagram HTML + - vertical (int) - threshold for formatting multiple alternatives vertically + instead of horizontally (default=3) + - show_results_names - bool flag whether diagram should show annotations for + defined results names + - show_groups - bool flag whether groups should be highlighted with an unlabeled surrounding box + Additional diagram-formatting keyword arguments can also be included; + see railroad.Diagram class. + """ + + try: + from .diagram import to_railroad, railroad_to_html + except ImportError as ie: + raise Exception( + "must ``pip install pyparsing[diagrams]`` to generate parser railroad diagrams" + ) from ie + + self.streamline() + + railroad = to_railroad( + self, + vertical=vertical, + show_results_names=show_results_names, + show_groups=show_groups, + diagram_kwargs=kwargs, + ) + if isinstance(output_html, (str, Path)): + with open(output_html, "w", encoding="utf-8") as diag_file: + diag_file.write(railroad_to_html(railroad)) + else: + # we were passed a file-like object, just write to it + output_html.write(railroad_to_html(railroad)) + + setDefaultWhitespaceChars = set_default_whitespace_chars + inlineLiteralsUsing = inline_literals_using + setResultsName = set_results_name + setBreak = set_break + setParseAction = set_parse_action + addParseAction = add_parse_action + addCondition = add_condition + setFailAction = set_fail_action + tryParse = try_parse + canParseNext = can_parse_next + resetCache = reset_cache + enableLeftRecursion = enable_left_recursion + enablePackrat = enable_packrat + parseString = parse_string + scanString = scan_string + searchString = search_string + transformString = transform_string + setWhitespaceChars = set_whitespace_chars + parseWithTabs = parse_with_tabs + setDebugActions = set_debug_actions + setDebug = set_debug + defaultName = default_name + setName = set_name + parseFile = parse_file + runTests = run_tests + ignoreWhitespace = ignore_whitespace + leaveWhitespace = leave_whitespace + + +class _PendingSkip(ParserElement): + # internal placeholder class to hold a place were '...' is added to a parser element, + # once another ParserElement is added, this placeholder will be replaced with a SkipTo + def __init__(self, expr: ParserElement, must_skip: bool = False): + super().__init__() + self.anchor = expr + self.must_skip = must_skip + + def _generateDefaultName(self): + return str(self.anchor + Empty()).replace("Empty", "...") + + def __add__(self, other) -> "ParserElement": + skipper = SkipTo(other).set_name("...")("_skipped*") + if self.must_skip: + + def must_skip(t): + if not t._skipped or t._skipped.as_list() == [""]: + del t[0] + t.pop("_skipped", None) + + def show_skip(t): + if t._skipped.as_list()[-1:] == [""]: + t.pop("_skipped") + t["_skipped"] = "missing <" + repr(self.anchor) + ">" + + return ( + self.anchor + skipper().add_parse_action(must_skip) + | skipper().add_parse_action(show_skip) + ) + other + + return self.anchor + skipper + other + + def __repr__(self): + return self.defaultName + + def parseImpl(self, *args): + raise Exception( + "use of `...` expression without following SkipTo target expression" + ) + + +class Token(ParserElement): + """Abstract :class:`ParserElement` subclass, for defining atomic + matching patterns. + """ + + def __init__(self): + super().__init__(savelist=False) + + def _generateDefaultName(self): + return type(self).__name__ + + +class Empty(Token): + """ + An empty token, will always match. + """ + + def __init__(self): + super().__init__() + self.mayReturnEmpty = True + self.mayIndexError = False + + +class NoMatch(Token): + """ + A token that will never match. + """ + + def __init__(self): + super().__init__() + self.mayReturnEmpty = True + self.mayIndexError = False + self.errmsg = "Unmatchable token" + + def parseImpl(self, instring, loc, doActions=True): + raise ParseException(instring, loc, self.errmsg, self) + + +class Literal(Token): + """ + Token to exactly match a specified string. + + Example:: + + Literal('blah').parse_string('blah') # -> ['blah'] + Literal('blah').parse_string('blahfooblah') # -> ['blah'] + Literal('blah').parse_string('bla') # -> Exception: Expected "blah" + + For case-insensitive matching, use :class:`CaselessLiteral`. + + For keyword matching (force word break before and after the matched string), + use :class:`Keyword` or :class:`CaselessKeyword`. + """ + + def __init__(self, match_string: str = "", *, matchString: str = ""): + super().__init__() + match_string = matchString or match_string + self.match = match_string + self.matchLen = len(match_string) + try: + self.firstMatchChar = match_string[0] + except IndexError: + raise ValueError("null string passed to Literal; use Empty() instead") + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + self.mayIndexError = False + + # Performance tuning: modify __class__ to select + # a parseImpl optimized for single-character check + if self.matchLen == 1 and type(self) is Literal: + self.__class__ = _SingleCharLiteral + + def _generateDefaultName(self): + return repr(self.match) + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar and instring.startswith( + self.match, loc + ): + return loc + self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) + + +class _SingleCharLiteral(Literal): + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] == self.firstMatchChar: + return loc + 1, self.match + raise ParseException(instring, loc, self.errmsg, self) + + +ParserElement._literalStringClass = Literal + + +class Keyword(Token): + """ + Token to exactly match a specified string as a keyword, that is, + it must be immediately followed by a non-keyword character. Compare + with :class:`Literal`: + + - ``Literal("if")`` will match the leading ``'if'`` in + ``'ifAndOnlyIf'``. + - ``Keyword("if")`` will not; it will only match the leading + ``'if'`` in ``'if x=1'``, or ``'if(y==2)'`` + + Accepts two optional constructor arguments in addition to the + keyword string: + + - ``identChars`` is a string of characters that would be valid + identifier characters, defaulting to all alphanumerics + "_" and + "$" + - ``caseless`` allows case-insensitive matching, default is ``False``. + + Example:: + + Keyword("start").parse_string("start") # -> ['start'] + Keyword("start").parse_string("starting") # -> Exception + + For case-insensitive matching, use :class:`CaselessKeyword`. + """ + + DEFAULT_KEYWORD_CHARS = alphanums + "_$" + + def __init__( + self, + match_string: str = "", + ident_chars: typing.Optional[str] = None, + caseless: bool = False, + *, + matchString: str = "", + identChars: typing.Optional[str] = None, + ): + super().__init__() + identChars = identChars or ident_chars + if identChars is None: + identChars = Keyword.DEFAULT_KEYWORD_CHARS + match_string = matchString or match_string + self.match = match_string + self.matchLen = len(match_string) + try: + self.firstMatchChar = match_string[0] + except IndexError: + raise ValueError("null string passed to Keyword; use Empty() instead") + self.errmsg = "Expected {} {}".format(type(self).__name__, self.name) + self.mayReturnEmpty = False + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = match_string.upper() + identChars = identChars.upper() + self.identChars = set(identChars) + + def _generateDefaultName(self): + return repr(self.match) + + def parseImpl(self, instring, loc, doActions=True): + errmsg = self.errmsg + errloc = loc + if self.caseless: + if instring[loc : loc + self.matchLen].upper() == self.caselessmatch: + if loc == 0 or instring[loc - 1].upper() not in self.identChars: + if ( + loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen].upper() not in self.identChars + ): + return loc + self.matchLen, self.match + else: + # followed by keyword char + errmsg += ", was immediately followed by keyword character" + errloc = loc + self.matchLen + else: + # preceded by keyword char + errmsg += ", keyword was immediately preceded by keyword character" + errloc = loc - 1 + # else no match just raise plain exception + + else: + if ( + instring[loc] == self.firstMatchChar + and self.matchLen == 1 + or instring.startswith(self.match, loc) + ): + if loc == 0 or instring[loc - 1] not in self.identChars: + if ( + loc >= len(instring) - self.matchLen + or instring[loc + self.matchLen] not in self.identChars + ): + return loc + self.matchLen, self.match + else: + # followed by keyword char + errmsg += ( + ", keyword was immediately followed by keyword character" + ) + errloc = loc + self.matchLen + else: + # preceded by keyword char + errmsg += ", keyword was immediately preceded by keyword character" + errloc = loc - 1 + # else no match just raise plain exception + + raise ParseException(instring, errloc, errmsg, self) + + @staticmethod + def set_default_keyword_chars(chars) -> None: + """ + Overrides the default characters used by :class:`Keyword` expressions. + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + + setDefaultKeywordChars = set_default_keyword_chars + + +class CaselessLiteral(Literal): + """ + Token to match a specified string, ignoring case of letters. + Note: the matched results will always be in the case of the given + match string, NOT the case of the input text. + + Example:: + + CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10") + # -> ['CMD', 'CMD', 'CMD'] + + (Contrast with example for :class:`CaselessKeyword`.) + """ + + def __init__(self, match_string: str = "", *, matchString: str = ""): + match_string = matchString or match_string + super().__init__(match_string.upper()) + # Preserve the defining literal. + self.returnString = match_string + self.errmsg = "Expected " + self.name + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc : loc + self.matchLen].upper() == self.match: + return loc + self.matchLen, self.returnString + raise ParseException(instring, loc, self.errmsg, self) + + +class CaselessKeyword(Keyword): + """ + Caseless version of :class:`Keyword`. + + Example:: + + CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10") + # -> ['CMD', 'CMD'] + + (Contrast with example for :class:`CaselessLiteral`.) + """ + + def __init__( + self, + match_string: str = "", + ident_chars: typing.Optional[str] = None, + *, + matchString: str = "", + identChars: typing.Optional[str] = None, + ): + identChars = identChars or ident_chars + match_string = matchString or match_string + super().__init__(match_string, identChars, caseless=True) + + +class CloseMatch(Token): + """A variation on :class:`Literal` which matches "close" matches, + that is, strings with at most 'n' mismatching characters. + :class:`CloseMatch` takes parameters: + + - ``match_string`` - string to be matched + - ``caseless`` - a boolean indicating whether to ignore casing when comparing characters + - ``max_mismatches`` - (``default=1``) maximum number of + mismatches allowed to count as a match + + The results from a successful parse will contain the matched text + from the input string and the following named results: + + - ``mismatches`` - a list of the positions within the + match_string where mismatches were found + - ``original`` - the original match_string used to compare + against the input string + + If ``mismatches`` is an empty list, then the match was an exact + match. + + Example:: + + patt = CloseMatch("ATCATCGAATGGA") + patt.parse_string("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) + patt.parse_string("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) + + # exact match + patt.parse_string("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) + + # close match allowing up to 2 mismatches + patt = CloseMatch("ATCATCGAATGGA", max_mismatches=2) + patt.parse_string("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) + """ + + def __init__( + self, + match_string: str, + max_mismatches: int = None, + *, + maxMismatches: int = 1, + caseless=False, + ): + maxMismatches = max_mismatches if max_mismatches is not None else maxMismatches + super().__init__() + self.match_string = match_string + self.maxMismatches = maxMismatches + self.errmsg = "Expected {!r} (with up to {} mismatches)".format( + self.match_string, self.maxMismatches + ) + self.caseless = caseless + self.mayIndexError = False + self.mayReturnEmpty = False + + def _generateDefaultName(self): + return "{}:{!r}".format(type(self).__name__, self.match_string) + + def parseImpl(self, instring, loc, doActions=True): + start = loc + instrlen = len(instring) + maxloc = start + len(self.match_string) + + if maxloc <= instrlen: + match_string = self.match_string + match_stringloc = 0 + mismatches = [] + maxMismatches = self.maxMismatches + + for match_stringloc, s_m in enumerate( + zip(instring[loc:maxloc], match_string) + ): + src, mat = s_m + if self.caseless: + src, mat = src.lower(), mat.lower() + + if src != mat: + mismatches.append(match_stringloc) + if len(mismatches) > maxMismatches: + break + else: + loc = start + match_stringloc + 1 + results = ParseResults([instring[start:loc]]) + results["original"] = match_string + results["mismatches"] = mismatches + return loc, results + + raise ParseException(instring, loc, self.errmsg, self) + + +class Word(Token): + """Token for matching words composed of allowed character sets. + Parameters: + - ``init_chars`` - string of all characters that should be used to + match as a word; "ABC" will match "AAA", "ABAB", "CBAC", etc.; + if ``body_chars`` is also specified, then this is the string of + initial characters + - ``body_chars`` - string of characters that + can be used for matching after a matched initial character as + given in ``init_chars``; if omitted, same as the initial characters + (default=``None``) + - ``min`` - minimum number of characters to match (default=1) + - ``max`` - maximum number of characters to match (default=0) + - ``exact`` - exact number of characters to match (default=0) + - ``as_keyword`` - match as a keyword (default=``False``) + - ``exclude_chars`` - characters that might be + found in the input ``body_chars`` string but which should not be + accepted for matching ;useful to define a word of all + printables except for one or two characters, for instance + (default=``None``) + + :class:`srange` is useful for defining custom character set strings + for defining :class:`Word` expressions, using range notation from + regular expression character sets. + + A common mistake is to use :class:`Word` to match a specific literal + string, as in ``Word("Address")``. Remember that :class:`Word` + uses the string argument to define *sets* of matchable characters. + This expression would match "Add", "AAA", "dAred", or any other word + made up of the characters 'A', 'd', 'r', 'e', and 's'. To match an + exact literal string, use :class:`Literal` or :class:`Keyword`. + + pyparsing includes helper strings for building Words: + + - :class:`alphas` + - :class:`nums` + - :class:`alphanums` + - :class:`hexnums` + - :class:`alphas8bit` (alphabetic characters in ASCII range 128-255 + - accented, tilded, umlauted, etc.) + - :class:`punc8bit` (non-alphabetic characters in ASCII range + 128-255 - currency, symbols, superscripts, diacriticals, etc.) + - :class:`printables` (any non-whitespace character) + + ``alphas``, ``nums``, and ``printables`` are also defined in several + Unicode sets - see :class:`pyparsing_unicode``. + + Example:: + + # a word composed of digits + integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9")) + + # a word with a leading capital, and zero or more lowercase + capital_word = Word(alphas.upper(), alphas.lower()) + + # hostnames are alphanumeric, with leading alpha, and '-' + hostname = Word(alphas, alphanums + '-') + + # roman numeral (not a strict parser, accepts invalid mix of characters) + roman = Word("IVXLCDM") + + # any string of non-whitespace characters, except for ',' + csv_value = Word(printables, exclude_chars=",") + """ + + def __init__( + self, + init_chars: str = "", + body_chars: typing.Optional[str] = None, + min: int = 1, + max: int = 0, + exact: int = 0, + as_keyword: bool = False, + exclude_chars: typing.Optional[str] = None, + *, + initChars: typing.Optional[str] = None, + bodyChars: typing.Optional[str] = None, + asKeyword: bool = False, + excludeChars: typing.Optional[str] = None, + ): + initChars = initChars or init_chars + bodyChars = bodyChars or body_chars + asKeyword = asKeyword or as_keyword + excludeChars = excludeChars or exclude_chars + super().__init__() + if not initChars: + raise ValueError( + "invalid {}, initChars cannot be empty string".format( + type(self).__name__ + ) + ) + + initChars = set(initChars) + self.initChars = initChars + if excludeChars: + excludeChars = set(excludeChars) + initChars -= excludeChars + if bodyChars: + bodyChars = set(bodyChars) - excludeChars + self.initCharsOrig = "".join(sorted(initChars)) + + if bodyChars: + self.bodyCharsOrig = "".join(sorted(bodyChars)) + self.bodyChars = set(bodyChars) + else: + self.bodyCharsOrig = "".join(sorted(initChars)) + self.bodyChars = set(initChars) + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError( + "cannot specify a minimum length < 1; use Opt(Word()) if zero-length word is permitted" + ) + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.asKeyword = asKeyword + + # see if we can make a regex for this Word + if " " not in self.initChars | self.bodyChars and (min == 1 and exact == 0): + if self.bodyChars == self.initChars: + if max == 0: + repeat = "+" + elif max == 1: + repeat = "" + else: + repeat = "{{{},{}}}".format( + self.minLen, "" if self.maxLen == _MAX_INT else self.maxLen + ) + self.reString = "[{}]{}".format( + _collapse_string_to_ranges(self.initChars), + repeat, + ) + elif len(self.initChars) == 1: + if max == 0: + repeat = "*" + else: + repeat = "{{0,{}}}".format(max - 1) + self.reString = "{}[{}]{}".format( + re.escape(self.initCharsOrig), + _collapse_string_to_ranges(self.bodyChars), + repeat, + ) + else: + if max == 0: + repeat = "*" + elif max == 2: + repeat = "" + else: + repeat = "{{0,{}}}".format(max - 1) + self.reString = "[{}][{}]{}".format( + _collapse_string_to_ranges(self.initChars), + _collapse_string_to_ranges(self.bodyChars), + repeat, + ) + if self.asKeyword: + self.reString = r"\b" + self.reString + r"\b" + + try: + self.re = re.compile(self.reString) + except re.error: + self.re = None + else: + self.re_match = self.re.match + self.__class__ = _WordRegex + + def _generateDefaultName(self): + def charsAsStr(s): + max_repr_len = 16 + s = _collapse_string_to_ranges(s, re_escape=False) + if len(s) > max_repr_len: + return s[: max_repr_len - 3] + "..." + else: + return s + + if self.initChars != self.bodyChars: + base = "W:({}, {})".format( + charsAsStr(self.initChars), charsAsStr(self.bodyChars) + ) + else: + base = "W:({})".format(charsAsStr(self.initChars)) + + # add length specification + if self.minLen > 1 or self.maxLen != _MAX_INT: + if self.minLen == self.maxLen: + if self.minLen == 1: + return base[2:] + else: + return base + "{{{}}}".format(self.minLen) + elif self.maxLen == _MAX_INT: + return base + "{{{},...}}".format(self.minLen) + else: + return base + "{{{},{}}}".format(self.minLen, self.maxLen) + return base + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not in self.initChars: + raise ParseException(instring, loc, self.errmsg, self) + + start = loc + loc += 1 + instrlen = len(instring) + bodychars = self.bodyChars + maxloc = start + self.maxLen + maxloc = min(maxloc, instrlen) + while loc < maxloc and instring[loc] in bodychars: + loc += 1 + + throwException = False + if loc - start < self.minLen: + throwException = True + elif self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + elif self.asKeyword: + if ( + start > 0 + and instring[start - 1] in bodychars + or loc < instrlen + and instring[loc] in bodychars + ): + throwException = True + + if throwException: + raise ParseException(instring, loc, self.errmsg, self) + + return loc, instring[start:loc] + + +class _WordRegex(Word): + def parseImpl(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + return loc, result.group() + + +class Char(_WordRegex): + """A short-cut class for defining :class:`Word` ``(characters, exact=1)``, + when defining a match of any single character in a string of + characters. + """ + + def __init__( + self, + charset: str, + as_keyword: bool = False, + exclude_chars: typing.Optional[str] = None, + *, + asKeyword: bool = False, + excludeChars: typing.Optional[str] = None, + ): + asKeyword = asKeyword or as_keyword + excludeChars = excludeChars or exclude_chars + super().__init__( + charset, exact=1, asKeyword=asKeyword, excludeChars=excludeChars + ) + self.reString = "[{}]".format(_collapse_string_to_ranges(self.initChars)) + if asKeyword: + self.reString = r"\b{}\b".format(self.reString) + self.re = re.compile(self.reString) + self.re_match = self.re.match + + +class Regex(Token): + r"""Token for matching strings that match a given regular + expression. Defined with string specifying the regular expression in + a form recognized by the stdlib Python `re module `_. + If the given regex contains named groups (defined using ``(?P...)``), + these will be preserved as named :class:`ParseResults`. + + If instead of the Python stdlib ``re`` module you wish to use a different RE module + (such as the ``regex`` module), you can do so by building your ``Regex`` object with + a compiled RE that was compiled using ``regex``. + + Example:: + + realnum = Regex(r"[+-]?\d+\.\d*") + # ref: https://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression + roman = Regex(r"M{0,4}(CM|CD|D?{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") + + # named fields in a regex will be returned as named results + date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)') + + # the Regex class will accept re's compiled using the regex module + import regex + parser = pp.Regex(regex.compile(r'[0-9]')) + """ + + def __init__( + self, + pattern: Any, + flags: Union[re.RegexFlag, int] = 0, + as_group_list: bool = False, + as_match: bool = False, + *, + asGroupList: bool = False, + asMatch: bool = False, + ): + """The parameters ``pattern`` and ``flags`` are passed + to the ``re.compile()`` function as-is. See the Python + `re module `_ module for an + explanation of the acceptable patterns and flags. + """ + super().__init__() + asGroupList = asGroupList or as_group_list + asMatch = asMatch or as_match + + if isinstance(pattern, str_type): + if not pattern: + raise ValueError("null string passed to Regex; use Empty() instead") + + self._re = None + self.reString = self.pattern = pattern + self.flags = flags + + elif hasattr(pattern, "pattern") and hasattr(pattern, "match"): + self._re = pattern + self.pattern = self.reString = pattern.pattern + self.flags = flags + + else: + raise TypeError( + "Regex may only be constructed with a string or a compiled RE object" + ) + + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.asGroupList = asGroupList + self.asMatch = asMatch + if self.asGroupList: + self.parseImpl = self.parseImplAsGroupList + if self.asMatch: + self.parseImpl = self.parseImplAsMatch + + @cached_property + def re(self): + if self._re: + return self._re + else: + try: + return re.compile(self.pattern, self.flags) + except re.error: + raise ValueError( + "invalid pattern ({!r}) passed to Regex".format(self.pattern) + ) + + @cached_property + def re_match(self): + return self.re.match + + @cached_property + def mayReturnEmpty(self): + return self.re_match("") is not None + + def _generateDefaultName(self): + return "Re:({})".format(repr(self.pattern).replace("\\\\", "\\")) + + def parseImpl(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = ParseResults(result.group()) + d = result.groupdict() + if d: + for k, v in d.items(): + ret[k] = v + return loc, ret + + def parseImplAsGroupList(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result.groups() + return loc, ret + + def parseImplAsMatch(self, instring, loc, doActions=True): + result = self.re_match(instring, loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result + return loc, ret + + def sub(self, repl: str) -> ParserElement: + r""" + Return :class:`Regex` with an attached parse action to transform the parsed + result as if called using `re.sub(expr, repl, string) `_. + + Example:: + + make_html = Regex(r"(\w+):(.*?):").sub(r"<\1>\2") + print(make_html.transform_string("h1:main title:")) + # prints "

main title

" + """ + if self.asGroupList: + raise TypeError("cannot use sub() with Regex(asGroupList=True)") + + if self.asMatch and callable(repl): + raise TypeError("cannot use sub() with a callable with Regex(asMatch=True)") + + if self.asMatch: + + def pa(tokens): + return tokens[0].expand(repl) + + else: + + def pa(tokens): + return self.re.sub(repl, tokens[0]) + + return self.add_parse_action(pa) + + +class QuotedString(Token): + r""" + Token for matching strings that are delimited by quoting characters. + + Defined with the following parameters: + + - ``quote_char`` - string of one or more characters defining the + quote delimiting string + - ``esc_char`` - character to re_escape quotes, typically backslash + (default= ``None``) + - ``esc_quote`` - special quote sequence to re_escape an embedded quote + string (such as SQL's ``""`` to re_escape an embedded ``"``) + (default= ``None``) + - ``multiline`` - boolean indicating whether quotes can span + multiple lines (default= ``False``) + - ``unquote_results`` - boolean indicating whether the matched text + should be unquoted (default= ``True``) + - ``end_quote_char`` - string of one or more characters defining the + end of the quote delimited string (default= ``None`` => same as + quote_char) + - ``convert_whitespace_escapes`` - convert escaped whitespace + (``'\t'``, ``'\n'``, etc.) to actual whitespace + (default= ``True``) + + Example:: + + qs = QuotedString('"') + print(qs.search_string('lsjdf "This is the quote" sldjf')) + complex_qs = QuotedString('{{', end_quote_char='}}') + print(complex_qs.search_string('lsjdf {{This is the "quote"}} sldjf')) + sql_qs = QuotedString('"', esc_quote='""') + print(sql_qs.search_string('lsjdf "This is the quote with ""embedded"" quotes" sldjf')) + + prints:: + + [['This is the quote']] + [['This is the "quote"']] + [['This is the quote with "embedded" quotes']] + """ + ws_map = ((r"\t", "\t"), (r"\n", "\n"), (r"\f", "\f"), (r"\r", "\r")) + + def __init__( + self, + quote_char: str = "", + esc_char: typing.Optional[str] = None, + esc_quote: typing.Optional[str] = None, + multiline: bool = False, + unquote_results: bool = True, + end_quote_char: typing.Optional[str] = None, + convert_whitespace_escapes: bool = True, + *, + quoteChar: str = "", + escChar: typing.Optional[str] = None, + escQuote: typing.Optional[str] = None, + unquoteResults: bool = True, + endQuoteChar: typing.Optional[str] = None, + convertWhitespaceEscapes: bool = True, + ): + super().__init__() + escChar = escChar or esc_char + escQuote = escQuote or esc_quote + unquoteResults = unquoteResults and unquote_results + endQuoteChar = endQuoteChar or end_quote_char + convertWhitespaceEscapes = ( + convertWhitespaceEscapes and convert_whitespace_escapes + ) + quote_char = quoteChar or quote_char + + # remove white space from quote chars - wont work anyway + quote_char = quote_char.strip() + if not quote_char: + raise ValueError("quote_char cannot be the empty string") + + if endQuoteChar is None: + endQuoteChar = quote_char + else: + endQuoteChar = endQuoteChar.strip() + if not endQuoteChar: + raise ValueError("endQuoteChar cannot be the empty string") + + self.quoteChar = quote_char + self.quoteCharLen = len(quote_char) + self.firstQuoteChar = quote_char[0] + self.endQuoteChar = endQuoteChar + self.endQuoteCharLen = len(endQuoteChar) + self.escChar = escChar + self.escQuote = escQuote + self.unquoteResults = unquoteResults + self.convertWhitespaceEscapes = convertWhitespaceEscapes + + sep = "" + inner_pattern = "" + + if escQuote: + inner_pattern += r"{}(?:{})".format(sep, re.escape(escQuote)) + sep = "|" + + if escChar: + inner_pattern += r"{}(?:{}.)".format(sep, re.escape(escChar)) + sep = "|" + self.escCharReplacePattern = re.escape(self.escChar) + "(.)" + + if len(self.endQuoteChar) > 1: + inner_pattern += ( + "{}(?:".format(sep) + + "|".join( + "(?:{}(?!{}))".format( + re.escape(self.endQuoteChar[:i]), + re.escape(self.endQuoteChar[i:]), + ) + for i in range(len(self.endQuoteChar) - 1, 0, -1) + ) + + ")" + ) + sep = "|" + + if multiline: + self.flags = re.MULTILINE | re.DOTALL + inner_pattern += r"{}(?:[^{}{}])".format( + sep, + _escape_regex_range_chars(self.endQuoteChar[0]), + (_escape_regex_range_chars(escChar) if escChar is not None else ""), + ) + else: + self.flags = 0 + inner_pattern += r"{}(?:[^{}\n\r{}])".format( + sep, + _escape_regex_range_chars(self.endQuoteChar[0]), + (_escape_regex_range_chars(escChar) if escChar is not None else ""), + ) + + self.pattern = "".join( + [ + re.escape(self.quoteChar), + "(?:", + inner_pattern, + ")*", + re.escape(self.endQuoteChar), + ] + ) + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + self.re_match = self.re.match + except re.error: + raise ValueError( + "invalid pattern {!r} passed to Regex".format(self.pattern) + ) + + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + + def _generateDefaultName(self): + if self.quoteChar == self.endQuoteChar and isinstance(self.quoteChar, str_type): + return "string enclosed in {!r}".format(self.quoteChar) + + return "quoted string, starting with {} ending with {}".format( + self.quoteChar, self.endQuoteChar + ) + + def parseImpl(self, instring, loc, doActions=True): + result = ( + instring[loc] == self.firstQuoteChar + and self.re_match(instring, loc) + or None + ) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result.group() + + if self.unquoteResults: + + # strip off quotes + ret = ret[self.quoteCharLen : -self.endQuoteCharLen] + + if isinstance(ret, str_type): + # replace escaped whitespace + if "\\" in ret and self.convertWhitespaceEscapes: + for wslit, wschar in self.ws_map: + ret = ret.replace(wslit, wschar) + + # replace escaped characters + if self.escChar: + ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) + + # replace escaped quotes + if self.escQuote: + ret = ret.replace(self.escQuote, self.endQuoteChar) + + return loc, ret + + +class CharsNotIn(Token): + """Token for matching words composed of characters *not* in a given + set (will include whitespace in matched characters if not listed in + the provided exclusion set - see example). Defined with string + containing all disallowed characters, and an optional minimum, + maximum, and/or exact length. The default value for ``min`` is + 1 (a minimum value < 1 is not valid); the default values for + ``max`` and ``exact`` are 0, meaning no maximum or exact + length restriction. + + Example:: + + # define a comma-separated-value as anything that is not a ',' + csv_value = CharsNotIn(',') + print(delimited_list(csv_value).parse_string("dkls,lsdkjf,s12 34,@!#,213")) + + prints:: + + ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] + """ + + def __init__( + self, + not_chars: str = "", + min: int = 1, + max: int = 0, + exact: int = 0, + *, + notChars: str = "", + ): + super().__init__() + self.skipWhitespace = False + self.notChars = not_chars or notChars + self.notCharsSet = set(self.notChars) + + if min < 1: + raise ValueError( + "cannot specify a minimum length < 1; use " + "Opt(CharsNotIn()) if zero-length char group is permitted" + ) + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = self.minLen == 0 + self.mayIndexError = False + + def _generateDefaultName(self): + not_chars_str = _collapse_string_to_ranges(self.notChars) + if len(not_chars_str) > 16: + return "!W:({}...)".format(self.notChars[: 16 - 3]) + else: + return "!W:({})".format(self.notChars) + + def parseImpl(self, instring, loc, doActions=True): + notchars = self.notCharsSet + if instring[loc] in notchars: + raise ParseException(instring, loc, self.errmsg, self) + + start = loc + loc += 1 + maxlen = min(start + self.maxLen, len(instring)) + while loc < maxlen and instring[loc] not in notchars: + loc += 1 + + if loc - start < self.minLen: + raise ParseException(instring, loc, self.errmsg, self) + + return loc, instring[start:loc] + + +class White(Token): + """Special matching class for matching whitespace. Normally, + whitespace is ignored by pyparsing grammars. This class is included + when some whitespace structures are significant. Define with + a string containing the whitespace characters to be matched; default + is ``" \\t\\r\\n"``. Also takes optional ``min``, + ``max``, and ``exact`` arguments, as defined for the + :class:`Word` class. + """ + + whiteStrs = { + " ": "", + "\t": "", + "\n": "", + "\r": "", + "\f": "", + "\u00A0": "", + "\u1680": "", + "\u180E": "", + "\u2000": "", + "\u2001": "", + "\u2002": "", + "\u2003": "", + "\u2004": "", + "\u2005": "", + "\u2006": "", + "\u2007": "", + "\u2008": "", + "\u2009": "", + "\u200A": "", + "\u200B": "", + "\u202F": "", + "\u205F": "", + "\u3000": "", + } + + def __init__(self, ws: str = " \t\r\n", min: int = 1, max: int = 0, exact: int = 0): + super().__init__() + self.matchWhite = ws + self.set_whitespace_chars( + "".join(c for c in self.whiteStrs if c not in self.matchWhite), + copy_defaults=True, + ) + # self.leave_whitespace() + self.mayReturnEmpty = True + self.errmsg = "Expected " + self.name + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + def _generateDefaultName(self): + return "".join(White.whiteStrs[c] for c in self.matchWhite) + + def parseImpl(self, instring, loc, doActions=True): + if instring[loc] not in self.matchWhite: + raise ParseException(instring, loc, self.errmsg, self) + start = loc + loc += 1 + maxloc = start + self.maxLen + maxloc = min(maxloc, len(instring)) + while loc < maxloc and instring[loc] in self.matchWhite: + loc += 1 + + if loc - start < self.minLen: + raise ParseException(instring, loc, self.errmsg, self) + + return loc, instring[start:loc] + + +class PositionToken(Token): + def __init__(self): + super().__init__() + self.mayReturnEmpty = True + self.mayIndexError = False + + +class GoToColumn(PositionToken): + """Token to advance to a specific column of input text; useful for + tabular report scraping. + """ + + def __init__(self, colno: int): + super().__init__() + self.col = colno + + def preParse(self, instring, loc): + if col(loc, instring) != self.col: + instrlen = len(instring) + if self.ignoreExprs: + loc = self._skipIgnorables(instring, loc) + while ( + loc < instrlen + and instring[loc].isspace() + and col(loc, instring) != self.col + ): + loc += 1 + return loc + + def parseImpl(self, instring, loc, doActions=True): + thiscol = col(loc, instring) + if thiscol > self.col: + raise ParseException(instring, loc, "Text not in expected column", self) + newloc = loc + self.col - thiscol + ret = instring[loc:newloc] + return newloc, ret + + +class LineStart(PositionToken): + r"""Matches if current position is at the beginning of a line within + the parse string + + Example:: + + test = '''\ + AAA this line + AAA and this line + AAA but not this one + B AAA and definitely not this one + ''' + + for t in (LineStart() + 'AAA' + restOfLine).search_string(test): + print(t) + + prints:: + + ['AAA', ' this line'] + ['AAA', ' and this line'] + + """ + + def __init__(self): + super().__init__() + self.leave_whitespace() + self.orig_whiteChars = set() | self.whiteChars + self.whiteChars.discard("\n") + self.skipper = Empty().set_whitespace_chars(self.whiteChars) + self.errmsg = "Expected start of line" + + def preParse(self, instring, loc): + if loc == 0: + return loc + else: + ret = self.skipper.preParse(instring, loc) + if "\n" in self.orig_whiteChars: + while instring[ret : ret + 1] == "\n": + ret = self.skipper.preParse(instring, ret + 1) + return ret + + def parseImpl(self, instring, loc, doActions=True): + if col(loc, instring) == 1: + return loc, [] + raise ParseException(instring, loc, self.errmsg, self) + + +class LineEnd(PositionToken): + """Matches if current position is at the end of a line within the + parse string + """ + + def __init__(self): + super().__init__() + self.whiteChars.discard("\n") + self.set_whitespace_chars(self.whiteChars, copy_defaults=False) + self.errmsg = "Expected end of line" + + def parseImpl(self, instring, loc, doActions=True): + if loc < len(instring): + if instring[loc] == "\n": + return loc + 1, "\n" + else: + raise ParseException(instring, loc, self.errmsg, self) + elif loc == len(instring): + return loc + 1, [] + else: + raise ParseException(instring, loc, self.errmsg, self) + + +class StringStart(PositionToken): + """Matches if current position is at the beginning of the parse + string + """ + + def __init__(self): + super().__init__() + self.errmsg = "Expected start of text" + + def parseImpl(self, instring, loc, doActions=True): + if loc != 0: + # see if entire string up to here is just whitespace and ignoreables + if loc != self.preParse(instring, 0): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + +class StringEnd(PositionToken): + """ + Matches if current position is at the end of the parse string + """ + + def __init__(self): + super().__init__() + self.errmsg = "Expected end of text" + + def parseImpl(self, instring, loc, doActions=True): + if loc < len(instring): + raise ParseException(instring, loc, self.errmsg, self) + elif loc == len(instring): + return loc + 1, [] + elif loc > len(instring): + return loc, [] + else: + raise ParseException(instring, loc, self.errmsg, self) + + +class WordStart(PositionToken): + """Matches if the current position is at the beginning of a + :class:`Word`, and is not preceded by any character in a given + set of ``word_chars`` (default= ``printables``). To emulate the + ``\b`` behavior of regular expressions, use + ``WordStart(alphanums)``. ``WordStart`` will also match at + the beginning of the string being parsed, or at the beginning of + a line. + """ + + def __init__(self, word_chars: str = printables, *, wordChars: str = printables): + wordChars = word_chars if wordChars == printables else wordChars + super().__init__() + self.wordChars = set(wordChars) + self.errmsg = "Not at the start of a word" + + def parseImpl(self, instring, loc, doActions=True): + if loc != 0: + if ( + instring[loc - 1] in self.wordChars + or instring[loc] not in self.wordChars + ): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + +class WordEnd(PositionToken): + """Matches if the current position is at the end of a :class:`Word`, + and is not followed by any character in a given set of ``word_chars`` + (default= ``printables``). To emulate the ``\b`` behavior of + regular expressions, use ``WordEnd(alphanums)``. ``WordEnd`` + will also match at the end of the string being parsed, or at the end + of a line. + """ + + def __init__(self, word_chars: str = printables, *, wordChars: str = printables): + wordChars = word_chars if wordChars == printables else wordChars + super().__init__() + self.wordChars = set(wordChars) + self.skipWhitespace = False + self.errmsg = "Not at the end of a word" + + def parseImpl(self, instring, loc, doActions=True): + instrlen = len(instring) + if instrlen > 0 and loc < instrlen: + if ( + instring[loc] in self.wordChars + or instring[loc - 1] not in self.wordChars + ): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + +class ParseExpression(ParserElement): + """Abstract subclass of ParserElement, for combining and + post-processing parsed tokens. + """ + + def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False): + super().__init__(savelist) + self.exprs: List[ParserElement] + if isinstance(exprs, _generatorType): + exprs = list(exprs) + + if isinstance(exprs, str_type): + self.exprs = [self._literalStringClass(exprs)] + elif isinstance(exprs, ParserElement): + self.exprs = [exprs] + elif isinstance(exprs, Iterable): + exprs = list(exprs) + # if sequence of strings provided, wrap with Literal + if any(isinstance(expr, str_type) for expr in exprs): + exprs = ( + self._literalStringClass(e) if isinstance(e, str_type) else e + for e in exprs + ) + self.exprs = list(exprs) + else: + try: + self.exprs = list(exprs) + except TypeError: + self.exprs = [exprs] + self.callPreparse = False + + def recurse(self) -> Sequence[ParserElement]: + return self.exprs[:] + + def append(self, other) -> ParserElement: + self.exprs.append(other) + self._defaultName = None + return self + + def leave_whitespace(self, recursive: bool = True) -> ParserElement: + """ + Extends ``leave_whitespace`` defined in base class, and also invokes ``leave_whitespace`` on + all contained expressions. + """ + super().leave_whitespace(recursive) + + if recursive: + self.exprs = [e.copy() for e in self.exprs] + for e in self.exprs: + e.leave_whitespace(recursive) + return self + + def ignore_whitespace(self, recursive: bool = True) -> ParserElement: + """ + Extends ``ignore_whitespace`` defined in base class, and also invokes ``leave_whitespace`` on + all contained expressions. + """ + super().ignore_whitespace(recursive) + if recursive: + self.exprs = [e.copy() for e in self.exprs] + for e in self.exprs: + e.ignore_whitespace(recursive) + return self + + def ignore(self, other) -> ParserElement: + if isinstance(other, Suppress): + if other not in self.ignoreExprs: + super().ignore(other) + for e in self.exprs: + e.ignore(self.ignoreExprs[-1]) + else: + super().ignore(other) + for e in self.exprs: + e.ignore(self.ignoreExprs[-1]) + return self + + def _generateDefaultName(self): + return "{}:({})".format(self.__class__.__name__, str(self.exprs)) + + def streamline(self) -> ParserElement: + if self.streamlined: + return self + + super().streamline() + + for e in self.exprs: + e.streamline() + + # collapse nested :class:`And`'s of the form ``And(And(And(a, b), c), d)`` to ``And(a, b, c, d)`` + # but only if there are no parse actions or resultsNames on the nested And's + # (likewise for :class:`Or`'s and :class:`MatchFirst`'s) + if len(self.exprs) == 2: + other = self.exprs[0] + if ( + isinstance(other, self.__class__) + and not other.parseAction + and other.resultsName is None + and not other.debug + ): + self.exprs = other.exprs[:] + [self.exprs[1]] + self._defaultName = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + other = self.exprs[-1] + if ( + isinstance(other, self.__class__) + and not other.parseAction + and other.resultsName is None + and not other.debug + ): + self.exprs = self.exprs[:-1] + other.exprs[:] + self._defaultName = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + self.errmsg = "Expected " + str(self) + + return self + + def validate(self, validateTrace=None) -> None: + tmp = (validateTrace if validateTrace is not None else [])[:] + [self] + for e in self.exprs: + e.validate(tmp) + self._checkRecursion([]) + + def copy(self) -> ParserElement: + ret = super().copy() + ret.exprs = [e.copy() for e in self.exprs] + return ret + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_ungrouped_named_tokens_in_collection + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in self.suppress_warnings_ + ): + for e in self.exprs: + if ( + isinstance(e, ParserElement) + and e.resultsName + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in e.suppress_warnings_ + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "collides with {!r} on contained expression".format( + "warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + ignoreWhitespace = ignore_whitespace + leaveWhitespace = leave_whitespace + + +class And(ParseExpression): + """ + Requires all given :class:`ParseExpression` s to be found in the given order. + Expressions may be separated by whitespace. + May be constructed using the ``'+'`` operator. + May also be constructed using the ``'-'`` operator, which will + suppress backtracking. + + Example:: + + integer = Word(nums) + name_expr = Word(alphas)[1, ...] + + expr = And([integer("id"), name_expr("name"), integer("age")]) + # more easily written as: + expr = integer("id") + name_expr("name") + integer("age") + """ + + class _ErrorStop(Empty): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.leave_whitespace() + + def _generateDefaultName(self): + return "-" + + def __init__( + self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True + ): + exprs: List[ParserElement] = list(exprs_arg) + if exprs and Ellipsis in exprs: + tmp = [] + for i, expr in enumerate(exprs): + if expr is Ellipsis: + if i < len(exprs) - 1: + skipto_arg: ParserElement = (Empty() + exprs[i + 1]).exprs[-1] + tmp.append(SkipTo(skipto_arg)("_skipped*")) + else: + raise Exception( + "cannot construct And with sequence ending in ..." + ) + else: + tmp.append(expr) + exprs[:] = tmp + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + if not isinstance(self.exprs[0], White): + self.set_whitespace_chars( + self.exprs[0].whiteChars, + copy_defaults=self.exprs[0].copyDefaultWhiteChars, + ) + self.skipWhitespace = self.exprs[0].skipWhitespace + else: + self.skipWhitespace = False + else: + self.mayReturnEmpty = True + self.callPreparse = True + + def streamline(self) -> ParserElement: + # collapse any _PendingSkip's + if self.exprs: + if any( + isinstance(e, ParseExpression) + and e.exprs + and isinstance(e.exprs[-1], _PendingSkip) + for e in self.exprs[:-1] + ): + for i, e in enumerate(self.exprs[:-1]): + if e is None: + continue + if ( + isinstance(e, ParseExpression) + and e.exprs + and isinstance(e.exprs[-1], _PendingSkip) + ): + e.exprs[-1] = e.exprs[-1] + self.exprs[i + 1] + self.exprs[i + 1] = None + self.exprs = [e for e in self.exprs if e is not None] + + super().streamline() + + # link any IndentedBlocks to the prior expression + for prev, cur in zip(self.exprs, self.exprs[1:]): + # traverse cur or any first embedded expr of cur looking for an IndentedBlock + # (but watch out for recursive grammar) + seen = set() + while cur: + if id(cur) in seen: + break + seen.add(id(cur)) + if isinstance(cur, IndentedBlock): + prev.add_parse_action( + lambda s, l, t, cur_=cur: setattr( + cur_, "parent_anchor", col(l, s) + ) + ) + break + subs = cur.recurse() + cur = next(iter(subs), None) + + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + return self + + def parseImpl(self, instring, loc, doActions=True): + # pass False as callPreParse arg to _parse for first element, since we already + # pre-parsed the string as part of our And pre-parsing + loc, resultlist = self.exprs[0]._parse( + instring, loc, doActions, callPreParse=False + ) + errorStop = False + for e in self.exprs[1:]: + # if isinstance(e, And._ErrorStop): + if type(e) is And._ErrorStop: + errorStop = True + continue + if errorStop: + try: + loc, exprtokens = e._parse(instring, loc, doActions) + except ParseSyntaxException: + raise + except ParseBaseException as pe: + pe.__traceback__ = None + raise ParseSyntaxException._from_exception(pe) + except IndexError: + raise ParseSyntaxException( + instring, len(instring), self.errmsg, self + ) + else: + loc, exprtokens = e._parse(instring, loc, doActions) + if exprtokens or exprtokens.haskeys(): + resultlist += exprtokens + return loc, resultlist + + def __iadd__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + return self.append(other) # And([self, other]) + + def _checkRecursion(self, parseElementList): + subRecCheckList = parseElementList[:] + [self] + for e in self.exprs: + e._checkRecursion(subRecCheckList) + if not e.mayReturnEmpty: + break + + def _generateDefaultName(self): + inner = " ".join(str(e) for e in self.exprs) + # strip off redundant inner {}'s + while len(inner) > 1 and inner[0 :: len(inner) - 1] == "{}": + inner = inner[1:-1] + return "{" + inner + "}" + + +class Or(ParseExpression): + """Requires that at least one :class:`ParseExpression` is found. If + two expressions match, the expression that matches the longest + string will be used. May be constructed using the ``'^'`` + operator. + + Example:: + + # construct Or using '^' operator + + number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) + print(number.search_string("123 3.1416 789")) + + prints:: + + [['123'], ['3.1416'], ['789']] + """ + + def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False): + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = all(e.skipWhitespace for e in self.exprs) + else: + self.mayReturnEmpty = True + + def streamline(self) -> ParserElement: + super().streamline() + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.saveAsList = any(e.saveAsList for e in self.exprs) + self.skipWhitespace = all( + e.skipWhitespace and not isinstance(e, White) for e in self.exprs + ) + else: + self.saveAsList = False + return self + + def parseImpl(self, instring, loc, doActions=True): + maxExcLoc = -1 + maxException = None + matches = [] + fatals = [] + if all(e.callPreparse for e in self.exprs): + loc = self.preParse(instring, loc) + for e in self.exprs: + try: + loc2 = e.try_parse(instring, loc, raise_fatal=True) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parserElement = e + fatals.append(pfe) + maxException = None + maxExcLoc = -1 + except ParseException as err: + if not fatals: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException( + instring, len(instring), e.errmsg, self + ) + maxExcLoc = len(instring) + else: + # save match among all matches, to retry longest to shortest + matches.append((loc2, e)) + + if matches: + # re-evaluate all matches in descending order of length of match, in case attached actions + # might change whether or how much they match of the input. + matches.sort(key=itemgetter(0), reverse=True) + + if not doActions: + # no further conditions or parse actions to change the selection of + # alternative, so the first match will be the best match + best_expr = matches[0][1] + return best_expr._parse(instring, loc, doActions) + + longest = -1, None + for loc1, expr1 in matches: + if loc1 <= longest[0]: + # already have a longer match than this one will deliver, we are done + return longest + + try: + loc2, toks = expr1._parse(instring, loc, doActions) + except ParseException as err: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + else: + if loc2 >= loc1: + return loc2, toks + # didn't match as much as before + elif loc2 > longest[0]: + longest = loc2, toks + + if longest != (-1, None): + return longest + + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement)))) + max_fatal = fatals[0] + raise max_fatal + + if maxException is not None: + maxException.msg = self.errmsg + raise maxException + else: + raise ParseException( + instring, loc, "no defined alternatives to match", self + ) + + def __ixor__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + return self.append(other) # Or([self, other]) + + def _generateDefaultName(self): + return "{" + " ^ ".join(str(e) for e in self.exprs) + "}" + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_multiple_tokens_in_named_alternation + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in self.suppress_warnings_ + ): + if any( + isinstance(e, And) + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in e.suppress_warnings_ + for e in self.exprs + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "will return a list of all parsed tokens in an And alternative, " + "in prior versions only the first token was returned; enclose " + "contained argument in Group".format( + "warn_multiple_tokens_in_named_alternation", + name, + type(self).__name__, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + +class MatchFirst(ParseExpression): + """Requires that at least one :class:`ParseExpression` is found. If + more than one expression matches, the first one listed is the one that will + match. May be constructed using the ``'|'`` operator. + + Example:: + + # construct MatchFirst using '|' operator + + # watch the order of expressions to match + number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) + print(number.search_string("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] + + # put more selective expression first + number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) + print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] + """ + + def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False): + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = all(e.skipWhitespace for e in self.exprs) + else: + self.mayReturnEmpty = True + + def streamline(self) -> ParserElement: + if self.streamlined: + return self + + super().streamline() + if self.exprs: + self.saveAsList = any(e.saveAsList for e in self.exprs) + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = all( + e.skipWhitespace and not isinstance(e, White) for e in self.exprs + ) + else: + self.saveAsList = False + self.mayReturnEmpty = True + return self + + def parseImpl(self, instring, loc, doActions=True): + maxExcLoc = -1 + maxException = None + + for e in self.exprs: + try: + return e._parse( + instring, + loc, + doActions, + ) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parserElement = e + raise + except ParseException as err: + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException( + instring, len(instring), e.errmsg, self + ) + maxExcLoc = len(instring) + + if maxException is not None: + maxException.msg = self.errmsg + raise maxException + else: + raise ParseException( + instring, loc, "no defined alternatives to match", self + ) + + def __ior__(self, other): + if isinstance(other, str_type): + other = self._literalStringClass(other) + return self.append(other) # MatchFirst([self, other]) + + def _generateDefaultName(self): + return "{" + " | ".join(str(e) for e in self.exprs) + "}" + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_multiple_tokens_in_named_alternation + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in self.suppress_warnings_ + ): + if any( + isinstance(e, And) + and Diagnostics.warn_multiple_tokens_in_named_alternation + not in e.suppress_warnings_ + for e in self.exprs + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "will return a list of all parsed tokens in an And alternative, " + "in prior versions only the first token was returned; enclose " + "contained argument in Group".format( + "warn_multiple_tokens_in_named_alternation", + name, + type(self).__name__, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + +class Each(ParseExpression): + """Requires all given :class:`ParseExpression` s to be found, but in + any order. Expressions may be separated by whitespace. + + May be constructed using the ``'&'`` operator. + + Example:: + + color = one_of("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") + shape_type = one_of("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON") + integer = Word(nums) + shape_attr = "shape:" + shape_type("shape") + posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn") + color_attr = "color:" + color("color") + size_attr = "size:" + integer("size") + + # use Each (using operator '&') to accept attributes in any order + # (shape and posn are required, color and size are optional) + shape_spec = shape_attr & posn_attr & Opt(color_attr) & Opt(size_attr) + + shape_spec.run_tests(''' + shape: SQUARE color: BLACK posn: 100, 120 + shape: CIRCLE size: 50 color: BLUE posn: 50,80 + color:GREEN size:20 shape:TRIANGLE posn:20,40 + ''' + ) + + prints:: + + shape: SQUARE color: BLACK posn: 100, 120 + ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']] + - color: BLACK + - posn: ['100', ',', '120'] + - x: 100 + - y: 120 + - shape: SQUARE + + + shape: CIRCLE size: 50 color: BLUE posn: 50,80 + ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']] + - color: BLUE + - posn: ['50', ',', '80'] + - x: 50 + - y: 80 + - shape: CIRCLE + - size: 50 + + + color: GREEN size: 20 shape: TRIANGLE posn: 20,40 + ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']] + - color: GREEN + - posn: ['20', ',', '40'] + - x: 20 + - y: 40 + - shape: TRIANGLE + - size: 20 + """ + + def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True): + super().__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + else: + self.mayReturnEmpty = True + self.skipWhitespace = True + self.initExprGroups = True + self.saveAsList = True + + def streamline(self) -> ParserElement: + super().streamline() + if self.exprs: + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + else: + self.mayReturnEmpty = True + return self + + def parseImpl(self, instring, loc, doActions=True): + if self.initExprGroups: + self.opt1map = dict( + (id(e.expr), e) for e in self.exprs if isinstance(e, Opt) + ) + opt1 = [e.expr for e in self.exprs if isinstance(e, Opt)] + opt2 = [ + e + for e in self.exprs + if e.mayReturnEmpty and not isinstance(e, (Opt, Regex, ZeroOrMore)) + ] + self.optionals = opt1 + opt2 + self.multioptionals = [ + e.expr.set_results_name(e.resultsName, list_all_matches=True) + for e in self.exprs + if isinstance(e, _MultipleMatch) + ] + self.multirequired = [ + e.expr.set_results_name(e.resultsName, list_all_matches=True) + for e in self.exprs + if isinstance(e, OneOrMore) + ] + self.required = [ + e for e in self.exprs if not isinstance(e, (Opt, ZeroOrMore, OneOrMore)) + ] + self.required += self.multirequired + self.initExprGroups = False + + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + multis = self.multioptionals[:] + matchOrder = [] + + keepMatching = True + failed = [] + fatals = [] + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + multis + failed.clear() + fatals.clear() + for e in tmpExprs: + try: + tmpLoc = e.try_parse(instring, tmpLoc, raise_fatal=True) + except ParseFatalException as pfe: + pfe.__traceback__ = None + pfe.parserElement = e + fatals.append(pfe) + failed.append(e) + except ParseException: + failed.append(e) + else: + matchOrder.append(self.opt1map.get(id(e), e)) + if e in tmpReqd: + tmpReqd.remove(e) + elif e in tmpOpt: + tmpOpt.remove(e) + if len(failed) == len(tmpExprs): + keepMatching = False + + # look for any ParseFatalExceptions + if fatals: + if len(fatals) > 1: + fatals.sort(key=lambda e: -e.loc) + if fatals[0].loc == fatals[1].loc: + fatals.sort(key=lambda e: (-e.loc, -len(str(e.parserElement)))) + max_fatal = fatals[0] + raise max_fatal + + if tmpReqd: + missing = ", ".join([str(e) for e in tmpReqd]) + raise ParseException( + instring, + loc, + "Missing one or more required elements ({})".format(missing), + ) + + # add any unmatched Opts, in case they have default values defined + matchOrder += [e for e in self.exprs if isinstance(e, Opt) and e.expr in tmpOpt] + + total_results = ParseResults([]) + for e in matchOrder: + loc, results = e._parse(instring, loc, doActions) + total_results += results + + return loc, total_results + + def _generateDefaultName(self): + return "{" + " & ".join(str(e) for e in self.exprs) + "}" + + +class ParseElementEnhance(ParserElement): + """Abstract subclass of :class:`ParserElement`, for combining and + post-processing parsed tokens. + """ + + def __init__(self, expr: Union[ParserElement, str], savelist: bool = False): + super().__init__(savelist) + if isinstance(expr, str_type): + if issubclass(self._literalStringClass, Token): + expr = self._literalStringClass(expr) + elif issubclass(type(self), self._literalStringClass): + expr = Literal(expr) + else: + expr = self._literalStringClass(Literal(expr)) + self.expr = expr + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.set_whitespace_chars( + expr.whiteChars, copy_defaults=expr.copyDefaultWhiteChars + ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def recurse(self) -> Sequence[ParserElement]: + return [self.expr] if self.expr is not None else [] + + def parseImpl(self, instring, loc, doActions=True): + if self.expr is not None: + return self.expr._parse(instring, loc, doActions, callPreParse=False) + else: + raise ParseException(instring, loc, "No expression defined", self) + + def leave_whitespace(self, recursive: bool = True) -> ParserElement: + super().leave_whitespace(recursive) + + if recursive: + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.leave_whitespace(recursive) + return self + + def ignore_whitespace(self, recursive: bool = True) -> ParserElement: + super().ignore_whitespace(recursive) + + if recursive: + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.ignore_whitespace(recursive) + return self + + def ignore(self, other) -> ParserElement: + if isinstance(other, Suppress): + if other not in self.ignoreExprs: + super().ignore(other) + if self.expr is not None: + self.expr.ignore(self.ignoreExprs[-1]) + else: + super().ignore(other) + if self.expr is not None: + self.expr.ignore(self.ignoreExprs[-1]) + return self + + def streamline(self) -> ParserElement: + super().streamline() + if self.expr is not None: + self.expr.streamline() + return self + + def _checkRecursion(self, parseElementList): + if self in parseElementList: + raise RecursiveGrammarException(parseElementList + [self]) + subRecCheckList = parseElementList[:] + [self] + if self.expr is not None: + self.expr._checkRecursion(subRecCheckList) + + def validate(self, validateTrace=None) -> None: + if validateTrace is None: + validateTrace = [] + tmp = validateTrace[:] + [self] + if self.expr is not None: + self.expr.validate(tmp) + self._checkRecursion([]) + + def _generateDefaultName(self): + return "{}:({})".format(self.__class__.__name__, str(self.expr)) + + ignoreWhitespace = ignore_whitespace + leaveWhitespace = leave_whitespace + + +class IndentedBlock(ParseElementEnhance): + """ + Expression to match one or more expressions at a given indentation level. + Useful for parsing text where structure is implied by indentation (like Python source code). + """ + + class _Indent(Empty): + def __init__(self, ref_col: int): + super().__init__() + self.errmsg = "expected indent at column {}".format(ref_col) + self.add_condition(lambda s, l, t: col(l, s) == ref_col) + + class _IndentGreater(Empty): + def __init__(self, ref_col: int): + super().__init__() + self.errmsg = "expected indent at column greater than {}".format(ref_col) + self.add_condition(lambda s, l, t: col(l, s) > ref_col) + + def __init__( + self, expr: ParserElement, *, recursive: bool = False, grouped: bool = True + ): + super().__init__(expr, savelist=True) + # if recursive: + # raise NotImplementedError("IndentedBlock with recursive is not implemented") + self._recursive = recursive + self._grouped = grouped + self.parent_anchor = 1 + + def parseImpl(self, instring, loc, doActions=True): + # advance parse position to non-whitespace by using an Empty() + # this should be the column to be used for all subsequent indented lines + anchor_loc = Empty().preParse(instring, loc) + + # see if self.expr matches at the current location - if not it will raise an exception + # and no further work is necessary + self.expr.try_parse(instring, anchor_loc, doActions) + + indent_col = col(anchor_loc, instring) + peer_detect_expr = self._Indent(indent_col) + + inner_expr = Empty() + peer_detect_expr + self.expr + if self._recursive: + sub_indent = self._IndentGreater(indent_col) + nested_block = IndentedBlock( + self.expr, recursive=self._recursive, grouped=self._grouped + ) + nested_block.set_debug(self.debug) + nested_block.parent_anchor = indent_col + inner_expr += Opt(sub_indent + nested_block) + + inner_expr.set_name(f"inner {hex(id(inner_expr))[-4:].upper()}@{indent_col}") + block = OneOrMore(inner_expr) + + trailing_undent = self._Indent(self.parent_anchor) | StringEnd() + + if self._grouped: + wrapper = Group + else: + wrapper = lambda expr: expr + return (wrapper(block) + Optional(trailing_undent)).parseImpl( + instring, anchor_loc, doActions + ) + + +class AtStringStart(ParseElementEnhance): + """Matches if expression matches at the beginning of the parse + string:: + + AtStringStart(Word(nums)).parse_string("123") + # prints ["123"] + + AtStringStart(Word(nums)).parse_string(" 123") + # raises ParseException + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + self.callPreparse = False + + def parseImpl(self, instring, loc, doActions=True): + if loc != 0: + raise ParseException(instring, loc, "not found at string start") + return super().parseImpl(instring, loc, doActions) + + +class AtLineStart(ParseElementEnhance): + r"""Matches if an expression matches at the beginning of a line within + the parse string + + Example:: + + test = '''\ + AAA this line + AAA and this line + AAA but not this one + B AAA and definitely not this one + ''' + + for t in (AtLineStart('AAA') + restOfLine).search_string(test): + print(t) + + prints:: + + ['AAA', ' this line'] + ['AAA', ' and this line'] + + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + self.callPreparse = False + + def parseImpl(self, instring, loc, doActions=True): + if col(loc, instring) != 1: + raise ParseException(instring, loc, "not found at line start") + return super().parseImpl(instring, loc, doActions) + + +class FollowedBy(ParseElementEnhance): + """Lookahead matching of the given parse expression. + ``FollowedBy`` does *not* advance the parsing position within + the input string, it only verifies that the specified parse + expression matches at the current position. ``FollowedBy`` + always returns a null token list. If any results names are defined + in the lookahead expression, those *will* be returned for access by + name. + + Example:: + + # use FollowedBy to match a label only if it is followed by a ':' + data_word = Word(alphas) + label = data_word + FollowedBy(':') + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + + attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint() + + prints:: + + [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + self.mayReturnEmpty = True + + def parseImpl(self, instring, loc, doActions=True): + # by using self._expr.parse and deleting the contents of the returned ParseResults list + # we keep any named results that were defined in the FollowedBy expression + _, ret = self.expr._parse(instring, loc, doActions=doActions) + del ret[:] + + return loc, ret + + +class PrecededBy(ParseElementEnhance): + """Lookbehind matching of the given parse expression. + ``PrecededBy`` does not advance the parsing position within the + input string, it only verifies that the specified parse expression + matches prior to the current position. ``PrecededBy`` always + returns a null token list, but if a results name is defined on the + given expression, it is returned. + + Parameters: + + - expr - expression that must match prior to the current parse + location + - retreat - (default= ``None``) - (int) maximum number of characters + to lookbehind prior to the current parse location + + If the lookbehind expression is a string, :class:`Literal`, + :class:`Keyword`, or a :class:`Word` or :class:`CharsNotIn` + with a specified exact or maximum length, then the retreat + parameter is not required. Otherwise, retreat must be specified to + give a maximum number of characters to look back from + the current parse position for a lookbehind match. + + Example:: + + # VB-style variable names with type prefixes + int_var = PrecededBy("#") + pyparsing_common.identifier + str_var = PrecededBy("$") + pyparsing_common.identifier + + """ + + def __init__( + self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None + ): + super().__init__(expr) + self.expr = self.expr().leave_whitespace() + self.mayReturnEmpty = True + self.mayIndexError = False + self.exact = False + if isinstance(expr, str_type): + retreat = len(expr) + self.exact = True + elif isinstance(expr, (Literal, Keyword)): + retreat = expr.matchLen + self.exact = True + elif isinstance(expr, (Word, CharsNotIn)) and expr.maxLen != _MAX_INT: + retreat = expr.maxLen + self.exact = True + elif isinstance(expr, PositionToken): + retreat = 0 + self.exact = True + self.retreat = retreat + self.errmsg = "not preceded by " + str(expr) + self.skipWhitespace = False + self.parseAction.append(lambda s, l, t: t.__delitem__(slice(None, None))) + + def parseImpl(self, instring, loc=0, doActions=True): + if self.exact: + if loc < self.retreat: + raise ParseException(instring, loc, self.errmsg) + start = loc - self.retreat + _, ret = self.expr._parse(instring, start) + else: + # retreat specified a maximum lookbehind window, iterate + test_expr = self.expr + StringEnd() + instring_slice = instring[max(0, loc - self.retreat) : loc] + last_expr = ParseException(instring, loc, self.errmsg) + for offset in range(1, min(loc, self.retreat + 1) + 1): + try: + # print('trying', offset, instring_slice, repr(instring_slice[loc - offset:])) + _, ret = test_expr._parse( + instring_slice, len(instring_slice) - offset + ) + except ParseBaseException as pbe: + last_expr = pbe + else: + break + else: + raise last_expr + return loc, ret + + +class Located(ParseElementEnhance): + """ + Decorates a returned token with its starting and ending + locations in the input string. + + This helper adds the following results names: + + - ``locn_start`` - location where matched expression begins + - ``locn_end`` - location where matched expression ends + - ``value`` - the actual parsed results + + Be careful if the input text contains ```` characters, you + may want to call :class:`ParserElement.parse_with_tabs` + + Example:: + + wd = Word(alphas) + for match in Located(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): + print(match) + + prints:: + + [0, ['ljsdf'], 5] + [8, ['lksdjjf'], 15] + [18, ['lkkjj'], 23] + + """ + + def parseImpl(self, instring, loc, doActions=True): + start = loc + loc, tokens = self.expr._parse(instring, start, doActions, callPreParse=False) + ret_tokens = ParseResults([start, tokens, loc]) + ret_tokens["locn_start"] = start + ret_tokens["value"] = tokens + ret_tokens["locn_end"] = loc + if self.resultsName: + # must return as a list, so that the name will be attached to the complete group + return loc, [ret_tokens] + else: + return loc, ret_tokens + + +class NotAny(ParseElementEnhance): + """ + Lookahead to disallow matching with the given parse expression. + ``NotAny`` does *not* advance the parsing position within the + input string, it only verifies that the specified parse expression + does *not* match at the current position. Also, ``NotAny`` does + *not* skip over leading whitespace. ``NotAny`` always returns + a null token list. May be constructed using the ``'~'`` operator. + + Example:: + + AND, OR, NOT = map(CaselessKeyword, "AND OR NOT".split()) + + # take care not to mistake keywords for identifiers + ident = ~(AND | OR | NOT) + Word(alphas) + boolean_term = Opt(NOT) + ident + + # very crude boolean expression - to support parenthesis groups and + # operation hierarchy, use infix_notation + boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...] + + # integers that are followed by "." are actually floats + integer = Word(nums) + ~Char(".") + """ + + def __init__(self, expr: Union[ParserElement, str]): + super().__init__(expr) + # do NOT use self.leave_whitespace(), don't want to propagate to exprs + # self.leave_whitespace() + self.skipWhitespace = False + + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, " + str(self.expr) + + def parseImpl(self, instring, loc, doActions=True): + if self.expr.can_parse_next(instring, loc): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + def _generateDefaultName(self): + return "~{" + str(self.expr) + "}" + + +class _MultipleMatch(ParseElementEnhance): + def __init__( + self, + expr: ParserElement, + stop_on: typing.Optional[Union[ParserElement, str]] = None, + *, + stopOn: typing.Optional[Union[ParserElement, str]] = None, + ): + super().__init__(expr) + stopOn = stopOn or stop_on + self.saveAsList = True + ender = stopOn + if isinstance(ender, str_type): + ender = self._literalStringClass(ender) + self.stopOn(ender) + + def stopOn(self, ender) -> ParserElement: + if isinstance(ender, str_type): + ender = self._literalStringClass(ender) + self.not_ender = ~ender if ender is not None else None + return self + + def parseImpl(self, instring, loc, doActions=True): + self_expr_parse = self.expr._parse + self_skip_ignorables = self._skipIgnorables + check_ender = self.not_ender is not None + if check_ender: + try_not_ender = self.not_ender.tryParse + + # must be at least one (but first see if we are the stopOn sentinel; + # if so, fail) + if check_ender: + try_not_ender(instring, loc) + loc, tokens = self_expr_parse(instring, loc, doActions) + try: + hasIgnoreExprs = not not self.ignoreExprs + while 1: + if check_ender: + try_not_ender(instring, loc) + if hasIgnoreExprs: + preloc = self_skip_ignorables(instring, loc) + else: + preloc = loc + loc, tmptokens = self_expr_parse(instring, preloc, doActions) + if tmptokens or tmptokens.haskeys(): + tokens += tmptokens + except (ParseException, IndexError): + pass + + return loc, tokens + + def _setResultsName(self, name, listAllMatches=False): + if ( + __diag__.warn_ungrouped_named_tokens_in_collection + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in self.suppress_warnings_ + ): + for e in [self.expr] + self.expr.recurse(): + if ( + isinstance(e, ParserElement) + and e.resultsName + and Diagnostics.warn_ungrouped_named_tokens_in_collection + not in e.suppress_warnings_ + ): + warnings.warn( + "{}: setting results name {!r} on {} expression " + "collides with {!r} on contained expression".format( + "warn_ungrouped_named_tokens_in_collection", + name, + type(self).__name__, + e.resultsName, + ), + stacklevel=3, + ) + + return super()._setResultsName(name, listAllMatches) + + +class OneOrMore(_MultipleMatch): + """ + Repetition of one or more of the given expression. + + Parameters: + - expr - expression that must match one or more times + - stop_on - (default= ``None``) - expression for a terminating sentinel + (only required if the sentinel would ordinarily match the repetition + expression) + + Example:: + + data_word = Word(alphas) + label = data_word + FollowedBy(':') + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join)) + + text = "shape: SQUARE posn: upper left color: BLACK" + attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] + + # use stop_on attribute for OneOrMore to avoid reading label string as part of the data + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + OneOrMore(attr_expr).parse_string(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] + + # could also be written as + (attr_expr * (1,)).parse_string(text).pprint() + """ + + def _generateDefaultName(self): + return "{" + str(self.expr) + "}..." + + +class ZeroOrMore(_MultipleMatch): + """ + Optional repetition of zero or more of the given expression. + + Parameters: + - ``expr`` - expression that must match zero or more times + - ``stop_on`` - expression for a terminating sentinel + (only required if the sentinel would ordinarily match the repetition + expression) - (default= ``None``) + + Example: similar to :class:`OneOrMore` + """ + + def __init__( + self, + expr: ParserElement, + stop_on: typing.Optional[Union[ParserElement, str]] = None, + *, + stopOn: typing.Optional[Union[ParserElement, str]] = None, + ): + super().__init__(expr, stopOn=stopOn or stop_on) + self.mayReturnEmpty = True + + def parseImpl(self, instring, loc, doActions=True): + try: + return super().parseImpl(instring, loc, doActions) + except (ParseException, IndexError): + return loc, ParseResults([], name=self.resultsName) + + def _generateDefaultName(self): + return "[" + str(self.expr) + "]..." + + +class _NullToken: + def __bool__(self): + return False + + def __str__(self): + return "" + + +class Opt(ParseElementEnhance): + """ + Optional matching of the given expression. + + Parameters: + - ``expr`` - expression that must match zero or more times + - ``default`` (optional) - value to be returned if the optional expression is not found. + + Example:: + + # US postal code can be a 5-digit zip, plus optional 4-digit qualifier + zip = Combine(Word(nums, exact=5) + Opt('-' + Word(nums, exact=4))) + zip.run_tests(''' + # traditional ZIP code + 12345 + + # ZIP+4 form + 12101-0001 + + # invalid ZIP + 98765- + ''') + + prints:: + + # traditional ZIP code + 12345 + ['12345'] + + # ZIP+4 form + 12101-0001 + ['12101-0001'] + + # invalid ZIP + 98765- + ^ + FAIL: Expected end of text (at char 5), (line:1, col:6) + """ + + __optionalNotMatched = _NullToken() + + def __init__( + self, expr: Union[ParserElement, str], default: Any = __optionalNotMatched + ): + super().__init__(expr, savelist=False) + self.saveAsList = self.expr.saveAsList + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl(self, instring, loc, doActions=True): + self_expr = self.expr + try: + loc, tokens = self_expr._parse(instring, loc, doActions, callPreParse=False) + except (ParseException, IndexError): + default_value = self.defaultValue + if default_value is not self.__optionalNotMatched: + if self_expr.resultsName: + tokens = ParseResults([default_value]) + tokens[self_expr.resultsName] = default_value + else: + tokens = [default_value] + else: + tokens = [] + return loc, tokens + + def _generateDefaultName(self): + inner = str(self.expr) + # strip off redundant inner {}'s + while len(inner) > 1 and inner[0 :: len(inner) - 1] == "{}": + inner = inner[1:-1] + return "[" + inner + "]" + + +Optional = Opt + + +class SkipTo(ParseElementEnhance): + """ + Token for skipping over all undefined text until the matched + expression is found. + + Parameters: + - ``expr`` - target expression marking the end of the data to be skipped + - ``include`` - if ``True``, the target expression is also parsed + (the skipped text and target expression are returned as a 2-element + list) (default= ``False``). + - ``ignore`` - (default= ``None``) used to define grammars (typically quoted strings and + comments) that might contain false matches to the target expression + - ``fail_on`` - (default= ``None``) define expressions that are not allowed to be + included in the skipped test; if found before the target expression is found, + the :class:`SkipTo` is not a match + + Example:: + + report = ''' + Outstanding Issues Report - 1 Jan 2000 + + # | Severity | Description | Days Open + -----+----------+-------------------------------------------+----------- + 101 | Critical | Intermittent system crash | 6 + 94 | Cosmetic | Spelling error on Login ('log|n') | 14 + 79 | Minor | System slow when running too many reports | 47 + ''' + integer = Word(nums) + SEP = Suppress('|') + # use SkipTo to simply match everything up until the next SEP + # - ignore quoted strings, so that a '|' character inside a quoted string does not match + # - parse action will call token.strip() for each matched token, i.e., the description body + string_data = SkipTo(SEP, ignore=quoted_string) + string_data.set_parse_action(token_map(str.strip)) + ticket_expr = (integer("issue_num") + SEP + + string_data("sev") + SEP + + string_data("desc") + SEP + + integer("days_open")) + + for tkt in ticket_expr.search_string(report): + print tkt.dump() + + prints:: + + ['101', 'Critical', 'Intermittent system crash', '6'] + - days_open: '6' + - desc: 'Intermittent system crash' + - issue_num: '101' + - sev: 'Critical' + ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14'] + - days_open: '14' + - desc: "Spelling error on Login ('log|n')" + - issue_num: '94' + - sev: 'Cosmetic' + ['79', 'Minor', 'System slow when running too many reports', '47'] + - days_open: '47' + - desc: 'System slow when running too many reports' + - issue_num: '79' + - sev: 'Minor' + """ + + def __init__( + self, + other: Union[ParserElement, str], + include: bool = False, + ignore: bool = None, + fail_on: typing.Optional[Union[ParserElement, str]] = None, + *, + failOn: Union[ParserElement, str] = None, + ): + super().__init__(other) + failOn = failOn or fail_on + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.saveAsList = False + if isinstance(failOn, str_type): + self.failOn = self._literalStringClass(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for " + str(self.expr) + + def parseImpl(self, instring, loc, doActions=True): + startloc = loc + instrlen = len(instring) + self_expr_parse = self.expr._parse + self_failOn_canParseNext = ( + self.failOn.canParseNext if self.failOn is not None else None + ) + self_ignoreExpr_tryParse = ( + self.ignoreExpr.tryParse if self.ignoreExpr is not None else None + ) + + tmploc = loc + while tmploc <= instrlen: + if self_failOn_canParseNext is not None: + # break if failOn expression matches + if self_failOn_canParseNext(instring, tmploc): + break + + if self_ignoreExpr_tryParse is not None: + # advance past ignore expressions + while 1: + try: + tmploc = self_ignoreExpr_tryParse(instring, tmploc) + except ParseBaseException: + break + + try: + self_expr_parse(instring, tmploc, doActions=False, callPreParse=False) + except (ParseException, IndexError): + # no match, advance loc in string + tmploc += 1 + else: + # matched skipto expr, done + break + + else: + # ran off the end of the input string without matching skipto expr, fail + raise ParseException(instring, loc, self.errmsg, self) + + # build up return values + loc = tmploc + skiptext = instring[startloc:loc] + skipresult = ParseResults(skiptext) + + if self.includeMatch: + loc, mat = self_expr_parse(instring, loc, doActions, callPreParse=False) + skipresult += mat + + return loc, skipresult + + +class Forward(ParseElementEnhance): + """ + Forward declaration of an expression to be defined later - + used for recursive grammars, such as algebraic infix notation. + When the expression is known, it is assigned to the ``Forward`` + variable using the ``'<<'`` operator. + + Note: take care when assigning to ``Forward`` not to overlook + precedence of operators. + + Specifically, ``'|'`` has a lower precedence than ``'<<'``, so that:: + + fwd_expr << a | b | c + + will actually be evaluated as:: + + (fwd_expr << a) | b | c + + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the ``Forward``:: + + fwd_expr << (a | b | c) + + Converting to use the ``'<<='`` operator instead will avoid this problem. + + See :class:`ParseResults.pprint` for an example of a recursive + parser created using ``Forward``. + """ + + def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None): + self.caller_frame = traceback.extract_stack(limit=2)[0] + super().__init__(other, savelist=False) + self.lshift_line = None + + def __lshift__(self, other): + if hasattr(self, "caller_frame"): + del self.caller_frame + if isinstance(other, str_type): + other = self._literalStringClass(other) + self.expr = other + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.set_whitespace_chars( + self.expr.whiteChars, copy_defaults=self.expr.copyDefaultWhiteChars + ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + self.lshift_line = traceback.extract_stack(limit=2)[-2] + return self + + def __ilshift__(self, other): + return self << other + + def __or__(self, other): + caller_line = traceback.extract_stack(limit=2)[-2] + if ( + __diag__.warn_on_match_first_with_lshift_operator + and caller_line == self.lshift_line + and Diagnostics.warn_on_match_first_with_lshift_operator + not in self.suppress_warnings_ + ): + warnings.warn( + "using '<<' operator with '|' is probably an error, use '<<='", + stacklevel=2, + ) + ret = super().__or__(other) + return ret + + def __del__(self): + # see if we are getting dropped because of '=' reassignment of var instead of '<<=' or '<<' + if ( + self.expr is None + and __diag__.warn_on_assignment_to_Forward + and Diagnostics.warn_on_assignment_to_Forward not in self.suppress_warnings_ + ): + warnings.warn_explicit( + "Forward defined here but no expression attached later using '<<=' or '<<'", + UserWarning, + filename=self.caller_frame.filename, + lineno=self.caller_frame.lineno, + ) + + def parseImpl(self, instring, loc, doActions=True): + if ( + self.expr is None + and __diag__.warn_on_parse_using_empty_Forward + and Diagnostics.warn_on_parse_using_empty_Forward + not in self.suppress_warnings_ + ): + # walk stack until parse_string, scan_string, search_string, or transform_string is found + parse_fns = [ + "parse_string", + "scan_string", + "search_string", + "transform_string", + ] + tb = traceback.extract_stack(limit=200) + for i, frm in enumerate(reversed(tb), start=1): + if frm.name in parse_fns: + stacklevel = i + 1 + break + else: + stacklevel = 2 + warnings.warn( + "Forward expression was never assigned a value, will not parse any input", + stacklevel=stacklevel, + ) + if not ParserElement._left_recursion_enabled: + return super().parseImpl(instring, loc, doActions) + # ## Bounded Recursion algorithm ## + # Recursion only needs to be processed at ``Forward`` elements, since they are + # the only ones that can actually refer to themselves. The general idea is + # to handle recursion stepwise: We start at no recursion, then recurse once, + # recurse twice, ..., until more recursion offers no benefit (we hit the bound). + # + # The "trick" here is that each ``Forward`` gets evaluated in two contexts + # - to *match* a specific recursion level, and + # - to *search* the bounded recursion level + # and the two run concurrently. The *search* must *match* each recursion level + # to find the best possible match. This is handled by a memo table, which + # provides the previous match to the next level match attempt. + # + # See also "Left Recursion in Parsing Expression Grammars", Medeiros et al. + # + # There is a complication since we not only *parse* but also *transform* via + # actions: We do not want to run the actions too often while expanding. Thus, + # we expand using `doActions=False` and only run `doActions=True` if the next + # recursion level is acceptable. + with ParserElement.recursion_lock: + memo = ParserElement.recursion_memos + try: + # we are parsing at a specific recursion expansion - use it as-is + prev_loc, prev_result = memo[loc, self, doActions] + if isinstance(prev_result, Exception): + raise prev_result + return prev_loc, prev_result.copy() + except KeyError: + act_key = (loc, self, True) + peek_key = (loc, self, False) + # we are searching for the best recursion expansion - keep on improving + # both `doActions` cases must be tracked separately here! + prev_loc, prev_peek = memo[peek_key] = ( + loc - 1, + ParseException( + instring, loc, "Forward recursion without base case", self + ), + ) + if doActions: + memo[act_key] = memo[peek_key] + while True: + try: + new_loc, new_peek = super().parseImpl(instring, loc, False) + except ParseException: + # we failed before getting any match – do not hide the error + if isinstance(prev_peek, Exception): + raise + new_loc, new_peek = prev_loc, prev_peek + # the match did not get better: we are done + if new_loc <= prev_loc: + if doActions: + # replace the match for doActions=False as well, + # in case the action did backtrack + prev_loc, prev_result = memo[peek_key] = memo[act_key] + del memo[peek_key], memo[act_key] + return prev_loc, prev_result.copy() + del memo[peek_key] + return prev_loc, prev_peek.copy() + # the match did get better: see if we can improve further + else: + if doActions: + try: + memo[act_key] = super().parseImpl(instring, loc, True) + except ParseException as e: + memo[peek_key] = memo[act_key] = (new_loc, e) + raise + prev_loc, prev_peek = memo[peek_key] = new_loc, new_peek + + def leave_whitespace(self, recursive: bool = True) -> ParserElement: + self.skipWhitespace = False + return self + + def ignore_whitespace(self, recursive: bool = True) -> ParserElement: + self.skipWhitespace = True + return self + + def streamline(self) -> ParserElement: + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate(self, validateTrace=None) -> None: + if validateTrace is None: + validateTrace = [] + + if self not in validateTrace: + tmp = validateTrace[:] + [self] + if self.expr is not None: + self.expr.validate(tmp) + self._checkRecursion([]) + + def _generateDefaultName(self): + # Avoid infinite recursion by setting a temporary _defaultName + self._defaultName = ": ..." + + # Use the string representation of main expression. + retString = "..." + try: + if self.expr is not None: + retString = str(self.expr)[:1000] + else: + retString = "None" + finally: + return self.__class__.__name__ + ": " + retString + + def copy(self) -> ParserElement: + if self.expr is not None: + return super().copy() + else: + ret = Forward() + ret <<= self + return ret + + def _setResultsName(self, name, list_all_matches=False): + if ( + __diag__.warn_name_set_on_empty_Forward + and Diagnostics.warn_name_set_on_empty_Forward + not in self.suppress_warnings_ + ): + if self.expr is None: + warnings.warn( + "{}: setting results name {!r} on {} expression " + "that has no contained expression".format( + "warn_name_set_on_empty_Forward", name, type(self).__name__ + ), + stacklevel=3, + ) + + return super()._setResultsName(name, list_all_matches) + + ignoreWhitespace = ignore_whitespace + leaveWhitespace = leave_whitespace + + +class TokenConverter(ParseElementEnhance): + """ + Abstract subclass of :class:`ParseExpression`, for converting parsed results. + """ + + def __init__(self, expr: Union[ParserElement, str], savelist=False): + super().__init__(expr) # , savelist) + self.saveAsList = False + + +class Combine(TokenConverter): + """Converter to concatenate all matching tokens to a single string. + By default, the matching patterns must also be contiguous in the + input string; this can be disabled by specifying + ``'adjacent=False'`` in the constructor. + + Example:: + + real = Word(nums) + '.' + Word(nums) + print(real.parse_string('3.1416')) # -> ['3', '.', '1416'] + # will also erroneously match the following + print(real.parse_string('3. 1416')) # -> ['3', '.', '1416'] + + real = Combine(Word(nums) + '.' + Word(nums)) + print(real.parse_string('3.1416')) # -> ['3.1416'] + # no match when there are internal spaces + print(real.parse_string('3. 1416')) # -> Exception: Expected W:(0123...) + """ + + def __init__( + self, + expr: ParserElement, + join_string: str = "", + adjacent: bool = True, + *, + joinString: typing.Optional[str] = None, + ): + super().__init__(expr) + joinString = joinString if joinString is not None else join_string + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leave_whitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + self.callPreparse = True + + def ignore(self, other) -> ParserElement: + if self.adjacent: + ParserElement.ignore(self, other) + else: + super().ignore(other) + return self + + def postParse(self, instring, loc, tokenlist): + retToks = tokenlist.copy() + del retToks[:] + retToks += ParseResults( + ["".join(tokenlist._asStringList(self.joinString))], modal=self.modalResults + ) + + if self.resultsName and retToks.haskeys(): + return [retToks] + else: + return retToks + + +class Group(TokenConverter): + """Converter to return the matched tokens as a list - useful for + returning tokens of :class:`ZeroOrMore` and :class:`OneOrMore` expressions. + + The optional ``aslist`` argument when set to True will return the + parsed tokens as a Python list instead of a pyparsing ParseResults. + + Example:: + + ident = Word(alphas) + num = Word(nums) + term = ident | num + func = ident + Opt(delimited_list(term)) + print(func.parse_string("fn a, b, 100")) + # -> ['fn', 'a', 'b', '100'] + + func = ident + Group(Opt(delimited_list(term))) + print(func.parse_string("fn a, b, 100")) + # -> ['fn', ['a', 'b', '100']] + """ + + def __init__(self, expr: ParserElement, aslist: bool = False): + super().__init__(expr) + self.saveAsList = True + self._asPythonList = aslist + + def postParse(self, instring, loc, tokenlist): + if self._asPythonList: + return ParseResults.List( + tokenlist.asList() + if isinstance(tokenlist, ParseResults) + else list(tokenlist) + ) + else: + return [tokenlist] + + +class Dict(TokenConverter): + """Converter to return a repetitive expression as a list, but also + as a dictionary. Each element can also be referenced using the first + token in the expression as its key. Useful for tabular report + scraping when the first column can be used as a item key. + + The optional ``asdict`` argument when set to True will return the + parsed tokens as a Python dict instead of a pyparsing ParseResults. + + Example:: + + data_word = Word(alphas) + label = data_word + FollowedBy(':') + + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + + # print attributes as plain groups + print(attr_expr[1, ...].parse_string(text).dump()) + + # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names + result = Dict(Group(attr_expr)[1, ...]).parse_string(text) + print(result.dump()) + + # access named fields as dict entries, or output as dict + print(result['shape']) + print(result.as_dict()) + + prints:: + + ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: 'light blue' + - posn: 'upper left' + - shape: 'SQUARE' + - texture: 'burlap' + SQUARE + {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} + + See more examples at :class:`ParseResults` of accessing fields by results name. + """ + + def __init__(self, expr: ParserElement, asdict: bool = False): + super().__init__(expr) + self.saveAsList = True + self._asPythonDict = asdict + + def postParse(self, instring, loc, tokenlist): + for i, tok in enumerate(tokenlist): + if len(tok) == 0: + continue + + ikey = tok[0] + if isinstance(ikey, int): + ikey = str(ikey).strip() + + if len(tok) == 1: + tokenlist[ikey] = _ParseResultsWithOffset("", i) + + elif len(tok) == 2 and not isinstance(tok[1], ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1], i) + + else: + try: + dictvalue = tok.copy() # ParseResults(i) + except Exception: + exc = TypeError( + "could not extract dict values from parsed results" + " - Dict expression must contain Grouped expressions" + ) + raise exc from None + + del dictvalue[0] + + if len(dictvalue) != 1 or ( + isinstance(dictvalue, ParseResults) and dictvalue.haskeys() + ): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue, i) + else: + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0], i) + + if self._asPythonDict: + return [tokenlist.as_dict()] if self.resultsName else tokenlist.as_dict() + else: + return [tokenlist] if self.resultsName else tokenlist + + +class Suppress(TokenConverter): + """Converter for ignoring the results of a parsed expression. + + Example:: + + source = "a, b, c,d" + wd = Word(alphas) + wd_list1 = wd + (',' + wd)[...] + print(wd_list1.parse_string(source)) + + # often, delimiters that are useful during parsing are just in the + # way afterward - use Suppress to keep them out of the parsed output + wd_list2 = wd + (Suppress(',') + wd)[...] + print(wd_list2.parse_string(source)) + + # Skipped text (using '...') can be suppressed as well + source = "lead in START relevant text END trailing text" + start_marker = Keyword("START") + end_marker = Keyword("END") + find_body = Suppress(...) + start_marker + ... + end_marker + print(find_body.parse_string(source) + + prints:: + + ['a', ',', 'b', ',', 'c', ',', 'd'] + ['a', 'b', 'c', 'd'] + ['START', 'relevant text ', 'END'] + + (See also :class:`delimited_list`.) + """ + + def __init__(self, expr: Union[ParserElement, str], savelist: bool = False): + if expr is ...: + expr = _PendingSkip(NoMatch()) + super().__init__(expr) + + def __add__(self, other) -> "ParserElement": + if isinstance(self.expr, _PendingSkip): + return Suppress(SkipTo(other)) + other + else: + return super().__add__(other) + + def __sub__(self, other) -> "ParserElement": + if isinstance(self.expr, _PendingSkip): + return Suppress(SkipTo(other)) - other + else: + return super().__sub__(other) + + def postParse(self, instring, loc, tokenlist): + return [] + + def suppress(self) -> ParserElement: + return self + + +def trace_parse_action(f: ParseAction) -> ParseAction: + """Decorator for debugging parse actions. + + When the parse action is called, this decorator will print + ``">> entering method-name(line:, , )"``. + When the parse action completes, the decorator will print + ``"<<"`` followed by the returned value, or any exception that the parse action raised. + + Example:: + + wd = Word(alphas) + + @trace_parse_action + def remove_duplicate_chars(tokens): + return ''.join(sorted(set(''.join(tokens)))) + + wds = wd[1, ...].set_parse_action(remove_duplicate_chars) + print(wds.parse_string("slkdjs sld sldd sdlf sdljf")) + + prints:: + + >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) + < 3: + thisFunc = paArgs[0].__class__.__name__ + "." + thisFunc + sys.stderr.write( + ">>entering {}(line: {!r}, {}, {!r})\n".format(thisFunc, line(l, s), l, t) + ) + try: + ret = f(*paArgs) + except Exception as exc: + sys.stderr.write("< str: + r"""Helper to easily define string ranges for use in :class:`Word` + construction. Borrows syntax from regexp ``'[]'`` string range + definitions:: + + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + + The input string must be enclosed in []'s, and the returned string + is the expanded character set joined into a single string. The + values enclosed in the []'s may be: + + - a single character + - an escaped character with a leading backslash (such as ``\-`` + or ``\]``) + - an escaped hex character with a leading ``'\x'`` + (``\x21``, which is a ``'!'`` character) (``\0x##`` + is also supported for backwards compatibility) + - an escaped octal character with a leading ``'\0'`` + (``\041``, which is a ``'!'`` character) + - a range of any of the above, separated by a dash (``'a-z'``, + etc.) + - any combination of the above (``'aeiouy'``, + ``'a-zA-Z0-9_$'``, etc.) + """ + _expanded = ( + lambda p: p + if not isinstance(p, ParseResults) + else "".join(chr(c) for c in range(ord(p[0]), ord(p[1]) + 1)) + ) + try: + return "".join(_expanded(part) for part in _reBracketExpr.parse_string(s).body) + except Exception: + return "" + + +def token_map(func, *args) -> ParseAction: + """Helper to define a parse action by mapping a function to all + elements of a :class:`ParseResults` list. If any additional args are passed, + they are forwarded to the given function as additional arguments + after the token, as in + ``hex_integer = Word(hexnums).set_parse_action(token_map(int, 16))``, + which will convert the parsed data to an integer using base 16. + + Example (compare the last to example in :class:`ParserElement.transform_string`:: + + hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16)) + hex_ints.run_tests(''' + 00 11 22 aa FF 0a 0d 1a + ''') + + upperword = Word(alphas).set_parse_action(token_map(str.upper)) + upperword[1, ...].run_tests(''' + my kingdom for a horse + ''') + + wd = Word(alphas).set_parse_action(token_map(str.title)) + wd[1, ...].set_parse_action(' '.join).run_tests(''' + now is the winter of our discontent made glorious summer by this sun of york + ''') + + prints:: + + 00 11 22 aa FF 0a 0d 1a + [0, 17, 34, 170, 255, 10, 13, 26] + + my kingdom for a horse + ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE'] + + now is the winter of our discontent made glorious summer by this sun of york + ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] + """ + + def pa(s, l, t): + return [func(tokn, *args) for tokn in t] + + func_name = getattr(func, "__name__", getattr(func, "__class__").__name__) + pa.__name__ = func_name + + return pa + + +def autoname_elements() -> None: + """ + Utility to simplify mass-naming of parser elements, for + generating railroad diagram with named subdiagrams. + """ + for name, var in sys._getframe().f_back.f_locals.items(): + if isinstance(var, ParserElement) and not var.customName: + var.set_name(name) + + +dbl_quoted_string = Combine( + Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"' +).set_name("string enclosed in double quotes") + +sgl_quoted_string = Combine( + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'" +).set_name("string enclosed in single quotes") + +quoted_string = Combine( + Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*') + '"' + | Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "'" +).set_name("quotedString using single or double quotes") + +unicode_string = Combine("u" + quoted_string.copy()).set_name("unicode string literal") + + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs: List[ParserElement] = [ + v for v in vars().values() if isinstance(v, ParserElement) +] + +# backward compatibility names +tokenMap = token_map +conditionAsParseAction = condition_as_parse_action +nullDebugAction = null_debug_action +sglQuotedString = sgl_quoted_string +dblQuotedString = dbl_quoted_string +quotedString = quoted_string +unicodeString = unicode_string +lineStart = line_start +lineEnd = line_end +stringStart = string_start +stringEnd = string_end +traceParseAction = trace_parse_action diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/diagram/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/diagram/__init__.py new file mode 100644 index 000000000..898644755 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/diagram/__init__.py @@ -0,0 +1,642 @@ +import railroad +import pyparsing +import typing +from typing import ( + List, + NamedTuple, + Generic, + TypeVar, + Dict, + Callable, + Set, + Iterable, +) +from jinja2 import Template +from io import StringIO +import inspect + + +jinja2_template_source = """\ + + + + {% if not head %} + + {% else %} + {{ head | safe }} + {% endif %} + + +{{ body | safe }} +{% for diagram in diagrams %} +
+

{{ diagram.title }}

+
{{ diagram.text }}
+
+ {{ diagram.svg }} +
+
+{% endfor %} + + +""" + +template = Template(jinja2_template_source) + +# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet +NamedDiagram = NamedTuple( + "NamedDiagram", + [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)], +) +""" +A simple structure for associating a name with a railroad diagram +""" + +T = TypeVar("T") + + +class EachItem(railroad.Group): + """ + Custom railroad item to compose a: + - Group containing a + - OneOrMore containing a + - Choice of the elements in the Each + with the group label indicating that all must be matched + """ + + all_label = "[ALL]" + + def __init__(self, *items): + choice_item = railroad.Choice(len(items) - 1, *items) + one_or_more_item = railroad.OneOrMore(item=choice_item) + super().__init__(one_or_more_item, label=self.all_label) + + +class AnnotatedItem(railroad.Group): + """ + Simple subclass of Group that creates an annotation label + """ + + def __init__(self, label: str, item): + super().__init__(item=item, label="[{}]".format(label) if label else label) + + +class EditablePartial(Generic[T]): + """ + Acts like a functools.partial, but can be edited. In other words, it represents a type that hasn't yet been + constructed. + """ + + # We need this here because the railroad constructors actually transform the data, so can't be called until the + # entire tree is assembled + + def __init__(self, func: Callable[..., T], args: list, kwargs: dict): + self.func = func + self.args = args + self.kwargs = kwargs + + @classmethod + def from_call(cls, func: Callable[..., T], *args, **kwargs) -> "EditablePartial[T]": + """ + If you call this function in the same way that you would call the constructor, it will store the arguments + as you expect. For example EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3) + """ + return EditablePartial(func=func, args=list(args), kwargs=kwargs) + + @property + def name(self): + return self.kwargs["name"] + + def __call__(self) -> T: + """ + Evaluate the partial and return the result + """ + args = self.args.copy() + kwargs = self.kwargs.copy() + + # This is a helpful hack to allow you to specify varargs parameters (e.g. *args) as keyword args (e.g. + # args=['list', 'of', 'things']) + arg_spec = inspect.getfullargspec(self.func) + if arg_spec.varargs in self.kwargs: + args += kwargs.pop(arg_spec.varargs) + + return self.func(*args, **kwargs) + + +def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str: + """ + Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams + :params kwargs: kwargs to be passed in to the template + """ + data = [] + for diagram in diagrams: + if diagram.diagram is None: + continue + io = StringIO() + diagram.diagram.writeSvg(io.write) + title = diagram.name + if diagram.index == 0: + title += " (root)" + data.append({"title": title, "text": "", "svg": io.getvalue()}) + + return template.render(diagrams=data, **kwargs) + + +def resolve_partial(partial: "EditablePartial[T]") -> T: + """ + Recursively resolves a collection of Partials into whatever type they are + """ + if isinstance(partial, EditablePartial): + partial.args = resolve_partial(partial.args) + partial.kwargs = resolve_partial(partial.kwargs) + return partial() + elif isinstance(partial, list): + return [resolve_partial(x) for x in partial] + elif isinstance(partial, dict): + return {key: resolve_partial(x) for key, x in partial.items()} + else: + return partial + + +def to_railroad( + element: pyparsing.ParserElement, + diagram_kwargs: typing.Optional[dict] = None, + vertical: int = 3, + show_results_names: bool = False, + show_groups: bool = False, +) -> List[NamedDiagram]: + """ + Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram + creation if you want to access the Railroad tree before it is converted to HTML + :param element: base element of the parser being diagrammed + :param diagram_kwargs: kwargs to pass to the Diagram() constructor + :param vertical: (optional) - int - limit at which number of alternatives should be + shown vertically instead of horizontally + :param show_results_names - bool to indicate whether results name annotations should be + included in the diagram + :param show_groups - bool to indicate whether groups should be highlighted with an unlabeled + surrounding box + """ + # Convert the whole tree underneath the root + lookup = ConverterState(diagram_kwargs=diagram_kwargs or {}) + _to_diagram_element( + element, + lookup=lookup, + parent=None, + vertical=vertical, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + root_id = id(element) + # Convert the root if it hasn't been already + if root_id in lookup: + if not element.customName: + lookup[root_id].name = "" + lookup[root_id].mark_for_extraction(root_id, lookup, force=True) + + # Now that we're finished, we can convert from intermediate structures into Railroad elements + diags = list(lookup.diagrams.values()) + if len(diags) > 1: + # collapse out duplicate diags with the same name + seen = set() + deduped_diags = [] + for d in diags: + # don't extract SkipTo elements, they are uninformative as subdiagrams + if d.name == "...": + continue + if d.name is not None and d.name not in seen: + seen.add(d.name) + deduped_diags.append(d) + resolved = [resolve_partial(partial) for partial in deduped_diags] + else: + # special case - if just one diagram, always display it, even if + # it has no name + resolved = [resolve_partial(partial) for partial in diags] + return sorted(resolved, key=lambda diag: diag.index) + + +def _should_vertical( + specification: int, exprs: Iterable[pyparsing.ParserElement] +) -> bool: + """ + Returns true if we should return a vertical list of elements + """ + if specification is None: + return False + else: + return len(_visible_exprs(exprs)) >= specification + + +class ElementState: + """ + State recorded for an individual pyparsing Element + """ + + # Note: this should be a dataclass, but we have to support Python 3.5 + def __init__( + self, + element: pyparsing.ParserElement, + converted: EditablePartial, + parent: EditablePartial, + number: int, + name: str = None, + parent_index: typing.Optional[int] = None, + ): + #: The pyparsing element that this represents + self.element: pyparsing.ParserElement = element + #: The name of the element + self.name: typing.Optional[str] = name + #: The output Railroad element in an unconverted state + self.converted: EditablePartial = converted + #: The parent Railroad element, which we store so that we can extract this if it's duplicated + self.parent: EditablePartial = parent + #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram + self.number: int = number + #: The index of this inside its parent + self.parent_index: typing.Optional[int] = parent_index + #: If true, we should extract this out into a subdiagram + self.extract: bool = False + #: If true, all of this element's children have been filled out + self.complete: bool = False + + def mark_for_extraction( + self, el_id: int, state: "ConverterState", name: str = None, force: bool = False + ): + """ + Called when this instance has been seen twice, and thus should eventually be extracted into a sub-diagram + :param el_id: id of the element + :param state: element/diagram state tracker + :param name: name to use for this element's text + :param force: If true, force extraction now, regardless of the state of this. Only useful for extracting the + root element when we know we're finished + """ + self.extract = True + + # Set the name + if not self.name: + if name: + # Allow forcing a custom name + self.name = name + elif self.element.customName: + self.name = self.element.customName + else: + self.name = "" + + # Just because this is marked for extraction doesn't mean we can do it yet. We may have to wait for children + # to be added + # Also, if this is just a string literal etc, don't bother extracting it + if force or (self.complete and _worth_extracting(self.element)): + state.extract_into_diagram(el_id) + + +class ConverterState: + """ + Stores some state that persists between recursions into the element tree + """ + + def __init__(self, diagram_kwargs: typing.Optional[dict] = None): + #: A dictionary mapping ParserElements to state relating to them + self._element_diagram_states: Dict[int, ElementState] = {} + #: A dictionary mapping ParserElement IDs to subdiagrams generated from them + self.diagrams: Dict[int, EditablePartial[NamedDiagram]] = {} + #: The index of the next unnamed element + self.unnamed_index: int = 1 + #: The index of the next element. This is used for sorting + self.index: int = 0 + #: Shared kwargs that are used to customize the construction of diagrams + self.diagram_kwargs: dict = diagram_kwargs or {} + self.extracted_diagram_names: Set[str] = set() + + def __setitem__(self, key: int, value: ElementState): + self._element_diagram_states[key] = value + + def __getitem__(self, key: int) -> ElementState: + return self._element_diagram_states[key] + + def __delitem__(self, key: int): + del self._element_diagram_states[key] + + def __contains__(self, key: int): + return key in self._element_diagram_states + + def generate_unnamed(self) -> int: + """ + Generate a number used in the name of an otherwise unnamed diagram + """ + self.unnamed_index += 1 + return self.unnamed_index + + def generate_index(self) -> int: + """ + Generate a number used to index a diagram + """ + self.index += 1 + return self.index + + def extract_into_diagram(self, el_id: int): + """ + Used when we encounter the same token twice in the same tree. When this + happens, we replace all instances of that token with a terminal, and + create a new subdiagram for the token + """ + position = self[el_id] + + # Replace the original definition of this element with a regular block + if position.parent: + ret = EditablePartial.from_call(railroad.NonTerminal, text=position.name) + if "item" in position.parent.kwargs: + position.parent.kwargs["item"] = ret + elif "items" in position.parent.kwargs: + position.parent.kwargs["items"][position.parent_index] = ret + + # If the element we're extracting is a group, skip to its content but keep the title + if position.converted.func == railroad.Group: + content = position.converted.kwargs["item"] + else: + content = position.converted + + self.diagrams[el_id] = EditablePartial.from_call( + NamedDiagram, + name=position.name, + diagram=EditablePartial.from_call( + railroad.Diagram, content, **self.diagram_kwargs + ), + index=position.number, + ) + + del self[el_id] + + +def _worth_extracting(element: pyparsing.ParserElement) -> bool: + """ + Returns true if this element is worth having its own sub-diagram. Simply, if any of its children + themselves have children, then its complex enough to extract + """ + children = element.recurse() + return any(child.recurse() for child in children) + + +def _apply_diagram_item_enhancements(fn): + """ + decorator to ensure enhancements to a diagram item (such as results name annotations) + get applied on return from _to_diagram_element (we do this since there are several + returns in _to_diagram_element) + """ + + def _inner( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, + ) -> typing.Optional[EditablePartial]: + + ret = fn( + element, + parent, + lookup, + vertical, + index, + name_hint, + show_results_names, + show_groups, + ) + + # apply annotation for results name, if present + if show_results_names and ret is not None: + element_results_name = element.resultsName + if element_results_name: + # add "*" to indicate if this is a "list all results" name + element_results_name += "" if element.modalResults else "*" + ret = EditablePartial.from_call( + railroad.Group, item=ret, label=element_results_name + ) + + return ret + + return _inner + + +def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]): + non_diagramming_exprs = ( + pyparsing.ParseElementEnhance, + pyparsing.PositionToken, + pyparsing.And._ErrorStop, + ) + return [ + e + for e in exprs + if not (e.customName or e.resultsName or isinstance(e, non_diagramming_exprs)) + ] + + +@_apply_diagram_item_enhancements +def _to_diagram_element( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, +) -> typing.Optional[EditablePartial]: + """ + Recursively converts a PyParsing Element to a railroad Element + :param lookup: The shared converter state that keeps track of useful things + :param index: The index of this element within the parent + :param parent: The parent of this element in the output tree + :param vertical: Controls at what point we make a list of elements vertical. If this is an integer (the default), + it sets the threshold of the number of items before we go vertical. If True, always go vertical, if False, never + do so + :param name_hint: If provided, this will override the generated name + :param show_results_names: bool flag indicating whether to add annotations for results names + :returns: The converted version of the input element, but as a Partial that hasn't yet been constructed + :param show_groups: bool flag indicating whether to show groups using bounding box + """ + exprs = element.recurse() + name = name_hint or element.customName or element.__class__.__name__ + + # Python's id() is used to provide a unique identifier for elements + el_id = id(element) + + element_results_name = element.resultsName + + # Here we basically bypass processing certain wrapper elements if they contribute nothing to the diagram + if not element.customName: + if isinstance( + element, + ( + # pyparsing.TokenConverter, + # pyparsing.Forward, + pyparsing.Located, + ), + ): + # However, if this element has a useful custom name, and its child does not, we can pass it on to the child + if exprs: + if not exprs[0].customName: + propagated_name = name + else: + propagated_name = None + + return _to_diagram_element( + element.expr, + parent=parent, + lookup=lookup, + vertical=vertical, + index=index, + name_hint=propagated_name, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # If the element isn't worth extracting, we always treat it as the first time we say it + if _worth_extracting(element): + if el_id in lookup: + # If we've seen this element exactly once before, we are only just now finding out that it's a duplicate, + # so we have to extract it into a new diagram. + looked_up = lookup[el_id] + looked_up.mark_for_extraction(el_id, lookup, name=name_hint) + ret = EditablePartial.from_call(railroad.NonTerminal, text=looked_up.name) + return ret + + elif el_id in lookup.diagrams: + # If we have seen the element at least twice before, and have already extracted it into a subdiagram, we + # just put in a marker element that refers to the sub-diagram + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + return ret + + # Recursively convert child elements + # Here we find the most relevant Railroad element for matching pyparsing Element + # We use ``items=[]`` here to hold the place for where the child elements will go once created + if isinstance(element, pyparsing.And): + # detect And's created with ``expr*N`` notation - for these use a OneOrMore with a repeat + # (all will have the same name, and resultsName) + if not exprs: + return None + if len(set((e.name, e.resultsName) for e in exprs)) == 1: + ret = EditablePartial.from_call( + railroad.OneOrMore, item="", repeat=str(len(exprs)) + ) + elif _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Stack, items=[]) + else: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)): + if not exprs: + return None + if _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Choice, 0, items=[]) + else: + ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[]) + elif isinstance(element, pyparsing.Each): + if not exprs: + return None + ret = EditablePartial.from_call(EachItem, items=[]) + elif isinstance(element, pyparsing.NotAny): + ret = EditablePartial.from_call(AnnotatedItem, label="NOT", item="") + elif isinstance(element, pyparsing.FollowedBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="") + elif isinstance(element, pyparsing.PrecededBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="") + elif isinstance(element, pyparsing.Group): + if show_groups: + ret = EditablePartial.from_call(AnnotatedItem, label="", item="") + else: + ret = EditablePartial.from_call(railroad.Group, label="", item="") + elif isinstance(element, pyparsing.TokenConverter): + ret = EditablePartial.from_call( + AnnotatedItem, label=type(element).__name__.lower(), item="" + ) + elif isinstance(element, pyparsing.Opt): + ret = EditablePartial.from_call(railroad.Optional, item="") + elif isinstance(element, pyparsing.OneOrMore): + ret = EditablePartial.from_call(railroad.OneOrMore, item="") + elif isinstance(element, pyparsing.ZeroOrMore): + ret = EditablePartial.from_call(railroad.ZeroOrMore, item="") + elif isinstance(element, pyparsing.Group): + ret = EditablePartial.from_call( + railroad.Group, item=None, label=element_results_name + ) + elif isinstance(element, pyparsing.Empty) and not element.customName: + # Skip unnamed "Empty" elements + ret = None + elif len(exprs) > 1: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif len(exprs) > 0 and not element_results_name: + ret = EditablePartial.from_call(railroad.Group, item="", label=name) + else: + terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName) + ret = terminal + + if ret is None: + return + + # Indicate this element's position in the tree so we can extract it if necessary + lookup[el_id] = ElementState( + element=element, + converted=ret, + parent=parent, + parent_index=index, + number=lookup.generate_index(), + ) + if element.customName: + lookup[el_id].mark_for_extraction(el_id, lookup, element.customName) + + i = 0 + for expr in exprs: + # Add a placeholder index in case we have to extract the child before we even add it to the parent + if "items" in ret.kwargs: + ret.kwargs["items"].insert(i, None) + + item = _to_diagram_element( + expr, + parent=ret, + lookup=lookup, + vertical=vertical, + index=i, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # Some elements don't need to be shown in the diagram + if item is not None: + if "item" in ret.kwargs: + ret.kwargs["item"] = item + elif "items" in ret.kwargs: + # If we've already extracted the child, don't touch this index, since it's occupied by a nonterminal + ret.kwargs["items"][i] = item + i += 1 + elif "items" in ret.kwargs: + # If we're supposed to skip this element, remove it from the parent + del ret.kwargs["items"][i] + + # If all this items children are none, skip this item + if ret and ( + ("items" in ret.kwargs and len(ret.kwargs["items"]) == 0) + or ("item" in ret.kwargs and ret.kwargs["item"] is None) + ): + ret = EditablePartial.from_call(railroad.Terminal, name) + + # Mark this element as "complete", ie it has all of its children + if el_id in lookup: + lookup[el_id].complete = True + + if el_id in lookup and lookup[el_id].extract and lookup[el_id].complete: + lookup.extract_into_diagram(el_id) + if ret is not None: + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + + return ret diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/exceptions.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/exceptions.py new file mode 100644 index 000000000..a38447bb0 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/exceptions.py @@ -0,0 +1,267 @@ +# exceptions.py + +import re +import sys +import typing + +from .util import col, line, lineno, _collapse_string_to_ranges +from .unicode import pyparsing_unicode as ppu + + +class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic): + pass + + +_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums) +_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.") + + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, + pstr: str, + loc: int = 0, + msg: typing.Optional[str] = None, + elem=None, + ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parser_element = self.parserElement = elem + self.args = (pstr, loc, msg) + + @staticmethod + def explain_exception(exc, depth=16): + """ + Method to take an exception and translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - exc - exception raised during parsing (need not be a ParseException, in support + of Python exceptions that might be raised in a parse action) + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + """ + import inspect + from .core import ParserElement + + if depth is None: + depth = sys.getrecursionlimit() + ret = [] + if isinstance(exc, ParseBaseException): + ret.append(exc.line) + ret.append(" " * (exc.column - 1) + "^") + ret.append("{}: {}".format(type(exc).__name__, exc)) + + if depth > 0: + callers = inspect.getinnerframes(exc.__traceback__, context=depth) + seen = set() + for i, ff in enumerate(callers[-depth:]): + frm = ff[0] + + f_self = frm.f_locals.get("self", None) + if isinstance(f_self, ParserElement): + if frm.f_code.co_name not in ("parseImpl", "_parseNoCache"): + continue + if id(f_self) in seen: + continue + seen.add(id(f_self)) + + self_type = type(f_self) + ret.append( + "{}.{} - {}".format( + self_type.__module__, self_type.__name__, f_self + ) + ) + + elif f_self is not None: + self_type = type(f_self) + ret.append("{}.{}".format(self_type.__module__, self_type.__name__)) + + else: + code = frm.f_code + if code.co_name in ("wrapper", ""): + continue + + ret.append("{}".format(code.co_name)) + + depth -= 1 + if not depth: + break + + return "\n".join(ret) + + @classmethod + def _from_exception(cls, pe): + """ + internal factory method to simplify creating one type of ParseException + from another - avoids having __init__ signature conflicts among subclasses + """ + return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) + + @property + def line(self) -> str: + """ + Return the line of text where the exception occurred. + """ + return line(self.loc, self.pstr) + + @property + def lineno(self) -> int: + """ + Return the 1-based line number of text where the exception occurred. + """ + return lineno(self.loc, self.pstr) + + @property + def col(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + @property + def column(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + def __str__(self) -> str: + if self.pstr: + if self.loc >= len(self.pstr): + foundstr = ", found end of text" + else: + # pull out next word at error location + found_match = _exception_word_extractor.match(self.pstr, self.loc) + if found_match is not None: + found = found_match.group(0) + else: + found = self.pstr[self.loc : self.loc + 1] + foundstr = (", found %r" % found).replace(r"\\", "\\") + else: + foundstr = "" + return "{}{} (at char {}), (line:{}, col:{})".format( + self.msg, foundstr, self.loc, self.lineno, self.column + ) + + def __repr__(self): + return str(self) + + def mark_input_line(self, marker_string: str = None, *, markerString=">!<") -> str: + """ + Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + markerString = marker_string if marker_string is not None else markerString + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( + (line_str[:line_column], markerString, line_str[line_column:]) + ) + return line_str.strip() + + def explain(self, depth=16) -> str: + """ + Method to translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + + Example:: + + expr = pp.Word(pp.nums) * 3 + try: + expr.parse_string("123 456 A789") + except pp.ParseException as pe: + print(pe.explain(depth=0)) + + prints:: + + 123 456 A789 + ^ + ParseException: Expected W:(0-9), found 'A' (at char 8), (line:1, col:9) + + Note: the diagnostic output will include string representations of the expressions + that failed to parse. These representations will be more helpful if you use `set_name` to + give identifiable names to your expressions. Otherwise they will use the default string + forms, which may be cryptic to read. + + Note: pyparsing's default truncation of exception tracebacks may also truncate the + stack of expressions that are displayed in the ``explain`` output. To get the full listing + of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True`` + """ + return self.explain_exception(self, depth) + + markInputline = mark_input_line + + +class ParseException(ParseBaseException): + """ + Exception thrown when a parse expression doesn't match the input string + + Example:: + + try: + Word(nums).set_name("integer").parse_string("ABC") + except ParseException as pe: + print(pe) + print("column: {}".format(pe.column)) + + prints:: + + Expected integer (at char 0), (line:1, col:1) + column: 1 + + """ + + +class ParseFatalException(ParseBaseException): + """ + User-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately + """ + + +class ParseSyntaxException(ParseFatalException): + """ + Just like :class:`ParseFatalException`, but thrown internally + when an :class:`ErrorStop` ('-' operator) indicates + that parsing is to stop immediately because an unbacktrackable + syntax error has been found. + """ + + +class RecursiveGrammarException(Exception): + """ + Exception thrown by :class:`ParserElement.validate` if the + grammar could be left-recursive; parser may need to enable + left recursion using :class:`ParserElement.enable_left_recursion` + """ + + def __init__(self, parseElementList): + self.parseElementTrace = parseElementList + + def __str__(self) -> str: + return "RecursiveGrammarException: {}".format(self.parseElementTrace) diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/helpers.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/helpers.py new file mode 100644 index 000000000..9588b3b78 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/helpers.py @@ -0,0 +1,1088 @@ +# helpers.py +import html.entities +import re +import typing + +from . import __diag__ +from .core import * +from .util import _bslash, _flatten, _escape_regex_range_chars + + +# +# global helpers +# +def delimited_list( + expr: Union[str, ParserElement], + delim: Union[str, ParserElement] = ",", + combine: bool = False, + min: typing.Optional[int] = None, + max: typing.Optional[int] = None, + *, + allow_trailing_delim: bool = False, +) -> ParserElement: + """Helper to define a delimited list of expressions - the delimiter + defaults to ','. By default, the list elements and delimiters can + have intervening whitespace, and comments, but this can be + overridden by passing ``combine=True`` in the constructor. If + ``combine`` is set to ``True``, the matching tokens are + returned as a single token string, with the delimiters included; + otherwise, the matching tokens are returned as a list of tokens, + with the delimiters suppressed. + + If ``allow_trailing_delim`` is set to True, then the list may end with + a delimiter. + + Example:: + + delimited_list(Word(alphas)).parse_string("aa,bb,cc") # -> ['aa', 'bb', 'cc'] + delimited_list(Word(hexnums), delim=':', combine=True).parse_string("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] + """ + if isinstance(expr, str_type): + expr = ParserElement._literalStringClass(expr) + + dlName = "{expr} [{delim} {expr}]...{end}".format( + expr=str(expr.copy().streamline()), + delim=str(delim), + end=" [{}]".format(str(delim)) if allow_trailing_delim else "", + ) + + if not combine: + delim = Suppress(delim) + + if min is not None: + if min < 1: + raise ValueError("min must be greater than 0") + min -= 1 + if max is not None: + if min is not None and max <= min: + raise ValueError("max must be greater than, or equal to min") + max -= 1 + delimited_list_expr = expr + (delim + expr)[min, max] + + if allow_trailing_delim: + delimited_list_expr += Opt(delim) + + if combine: + return Combine(delimited_list_expr).set_name(dlName) + else: + return delimited_list_expr.set_name(dlName) + + +def counted_array( + expr: ParserElement, + int_expr: typing.Optional[ParserElement] = None, + *, + intExpr: typing.Optional[ParserElement] = None, +) -> ParserElement: + """Helper to define a counted list of expressions. + + This helper defines a pattern of the form:: + + integer expr expr expr... + + where the leading integer tells how many expr expressions follow. + The matched tokens returns the array of expr tokens as a list - the + leading count token is suppressed. + + If ``int_expr`` is specified, it should be a pyparsing expression + that produces an integer value. + + Example:: + + counted_array(Word(alphas)).parse_string('2 ab cd ef') # -> ['ab', 'cd'] + + # in this parser, the leading integer value is given in binary, + # '10' indicating that 2 values are in the array + binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2)) + counted_array(Word(alphas), int_expr=binary_constant).parse_string('10 ab cd ef') # -> ['ab', 'cd'] + + # if other fields must be parsed after the count but before the + # list items, give the fields results names and they will + # be preserved in the returned ParseResults: + count_with_metadata = integer + Word(alphas)("type") + typed_array = counted_array(Word(alphanums), int_expr=count_with_metadata)("items") + result = typed_array.parse_string("3 bool True True False") + print(result.dump()) + + # prints + # ['True', 'True', 'False'] + # - items: ['True', 'True', 'False'] + # - type: 'bool' + """ + intExpr = intExpr or int_expr + array_expr = Forward() + + def count_field_parse_action(s, l, t): + nonlocal array_expr + n = t[0] + array_expr <<= (expr * n) if n else Empty() + # clear list contents, but keep any named results + del t[:] + + if intExpr is None: + intExpr = Word(nums).set_parse_action(lambda t: int(t[0])) + else: + intExpr = intExpr.copy() + intExpr.set_name("arrayLen") + intExpr.add_parse_action(count_field_parse_action, call_during_try=True) + return (intExpr + array_expr).set_name("(len) " + str(expr) + "...") + + +def match_previous_literal(expr: ParserElement) -> ParserElement: + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks for + a 'repeat' of a previous expression. For example:: + + first = Word(nums) + second = match_previous_literal(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches a previous literal, will also match the leading + ``"1:1"`` in ``"1:10"``. If this is not desired, use + :class:`match_previous_expr`. Do *not* use with packrat parsing + enabled. + """ + rep = Forward() + + def copy_token_to_repeater(s, l, t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.as_list()) + rep << And(Literal(tt) for tt in tflat) + else: + rep << Empty() + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def match_previous_expr(expr: ParserElement) -> ParserElement: + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks for + a 'repeat' of a previous expression. For example:: + + first = Word(nums) + second = match_previous_expr(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches by expressions, will *not* match the leading ``"1:1"`` + in ``"1:10"``; the expressions are evaluated first, and then + compared, so ``"1"`` is compared with ``"10"``. Do *not* use + with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep <<= e2 + + def copy_token_to_repeater(s, l, t): + matchTokens = _flatten(t.as_list()) + + def must_match_these_tokens(s, l, t): + theseTokens = _flatten(t.as_list()) + if theseTokens != matchTokens: + raise ParseException( + s, l, "Expected {}, found{}".format(matchTokens, theseTokens) + ) + + rep.set_parse_action(must_match_these_tokens, callDuringTry=True) + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def one_of( + strs: Union[typing.Iterable[str], str], + caseless: bool = False, + use_regex: bool = True, + as_keyword: bool = False, + *, + useRegex: bool = True, + asKeyword: bool = False, +) -> ParserElement: + """Helper to quickly define a set of alternative :class:`Literal` s, + and makes sure to do longest-first testing when there is a conflict, + regardless of the input order, but returns + a :class:`MatchFirst` for best performance. + + Parameters: + + - ``strs`` - a string of space-delimited literals, or a collection of + string literals + - ``caseless`` - treat all literals as caseless - (default= ``False``) + - ``use_regex`` - as an optimization, will + generate a :class:`Regex` object; otherwise, will generate + a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if + creating a :class:`Regex` raises an exception) - (default= ``True``) + - ``as_keyword`` - enforce :class:`Keyword`-style matching on the + generated expressions - (default= ``False``) + - ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 compatibility, + but will be removed in a future release + + Example:: + + comp_oper = one_of("< = > <= >= !=") + var = Word(alphas) + number = Word(nums) + term = var | number + comparison_expr = term + comp_oper + term + print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12")) + + prints:: + + [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] + """ + asKeyword = asKeyword or as_keyword + useRegex = useRegex and use_regex + + if ( + isinstance(caseless, str_type) + and __diag__.warn_on_multiple_string_args_to_oneof + ): + warnings.warn( + "More than one string argument passed to one_of, pass" + " choices as a list or space-delimited string", + stacklevel=2, + ) + + if caseless: + isequal = lambda a, b: a.upper() == b.upper() + masks = lambda a, b: b.upper().startswith(a.upper()) + parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral + else: + isequal = lambda a, b: a == b + masks = lambda a, b: b.startswith(a) + parseElementClass = Keyword if asKeyword else Literal + + symbols: List[str] = [] + if isinstance(strs, str_type): + symbols = strs.split() + elif isinstance(strs, Iterable): + symbols = list(strs) + else: + raise TypeError("Invalid argument to one_of, expected string or iterable") + if not symbols: + return NoMatch() + + # reorder given symbols to take care to avoid masking longer choices with shorter ones + # (but only if the given symbols are not just single characters) + if any(len(sym) > 1 for sym in symbols): + i = 0 + while i < len(symbols) - 1: + cur = symbols[i] + for j, other in enumerate(symbols[i + 1 :]): + if isequal(other, cur): + del symbols[i + j + 1] + break + elif masks(cur, other): + del symbols[i + j + 1] + symbols.insert(i, other) + break + else: + i += 1 + + if useRegex: + re_flags: int = re.IGNORECASE if caseless else 0 + + try: + if all(len(sym) == 1 for sym in symbols): + # symbols are just single characters, create range regex pattern + patt = "[{}]".format( + "".join(_escape_regex_range_chars(sym) for sym in symbols) + ) + else: + patt = "|".join(re.escape(sym) for sym in symbols) + + # wrap with \b word break markers if defining as keywords + if asKeyword: + patt = r"\b(?:{})\b".format(patt) + + ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols)) + + if caseless: + # add parse action to return symbols as specified, not in random + # casing as found in input string + symbol_map = {sym.lower(): sym for sym in symbols} + ret.add_parse_action(lambda s, l, t: symbol_map[t[0].lower()]) + + return ret + + except re.error: + warnings.warn( + "Exception creating Regex for one_of, building MatchFirst", stacklevel=2 + ) + + # last resort, just use MatchFirst + return MatchFirst(parseElementClass(sym) for sym in symbols).set_name( + " | ".join(symbols) + ) + + +def dict_of(key: ParserElement, value: ParserElement) -> ParserElement: + """Helper to easily and clearly define a dictionary by specifying + the respective patterns for the key and value. Takes care of + defining the :class:`Dict`, :class:`ZeroOrMore`, and + :class:`Group` tokens in the proper order. The key pattern + can include delimiting markers or punctuation, as long as they are + suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the :class:`Dict` results + can include named token fields. + + Example:: + + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + print(attr_expr[1, ...].parse_string(text).dump()) + + attr_label = label + attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) + + # similar to Dict, but simpler call format + result = dict_of(attr_label, attr_value).parse_string(text) + print(result.dump()) + print(result['shape']) + print(result.shape) # object attribute access works too + print(result.as_dict()) + + prints:: + + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: 'light blue' + - posn: 'upper left' + - shape: 'SQUARE' + - texture: 'burlap' + SQUARE + SQUARE + {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} + """ + return Dict(OneOrMore(Group(key + value))) + + +def original_text_for( + expr: ParserElement, as_string: bool = True, *, asString: bool = True +) -> ParserElement: + """Helper to return the original, untokenized text for a given + expression. Useful to restore the parsed fields of an HTML start + tag into the raw tag text itself, or to revert separate tokens with + intervening whitespace back to the original matching input text. By + default, returns astring containing the original parsed text. + + If the optional ``as_string`` argument is passed as + ``False``, then the return value is + a :class:`ParseResults` containing any results names that + were originally matched, and a single token containing the original + matched text from the input string. So if the expression passed to + :class:`original_text_for` contains expressions with defined + results names, you must set ``as_string`` to ``False`` if you + want to preserve those results name values. + + The ``asString`` pre-PEP8 argument is retained for compatibility, + but will be removed in a future release. + + Example:: + + src = "this is test bold text normal text " + for tag in ("b", "i"): + opener, closer = make_html_tags(tag) + patt = original_text_for(opener + SkipTo(closer) + closer) + print(patt.search_string(src)[0]) + + prints:: + + [' bold text '] + ['text'] + """ + asString = asString and as_string + + locMarker = Empty().set_parse_action(lambda s, loc, t: loc) + endlocMarker = locMarker.copy() + endlocMarker.callPreparse = False + matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") + if asString: + extractText = lambda s, l, t: s[t._original_start : t._original_end] + else: + + def extractText(s, l, t): + t[:] = [s[t.pop("_original_start") : t.pop("_original_end")]] + + matchExpr.set_parse_action(extractText) + matchExpr.ignoreExprs = expr.ignoreExprs + matchExpr.suppress_warning(Diagnostics.warn_ungrouped_named_tokens_in_collection) + return matchExpr + + +def ungroup(expr: ParserElement) -> ParserElement: + """Helper to undo pyparsing's default grouping of And expressions, + even if all but one are non-empty. + """ + return TokenConverter(expr).add_parse_action(lambda t: t[0]) + + +def locatedExpr(expr: ParserElement) -> ParserElement: + """ + (DEPRECATED - future code should use the Located class) + Helper to decorate a returned token with its starting and ending + locations in the input string. + + This helper adds the following results names: + + - ``locn_start`` - location where matched expression begins + - ``locn_end`` - location where matched expression ends + - ``value`` - the actual parsed results + + Be careful if the input text contains ```` characters, you + may want to call :class:`ParserElement.parseWithTabs` + + Example:: + + wd = Word(alphas) + for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"): + print(match) + + prints:: + + [[0, 'ljsdf', 5]] + [[8, 'lksdjjf', 15]] + [[18, 'lkkjj', 23]] + """ + locator = Empty().set_parse_action(lambda ss, ll, tt: ll) + return Group( + locator("locn_start") + + expr("value") + + locator.copy().leaveWhitespace()("locn_end") + ) + + +def nested_expr( + opener: Union[str, ParserElement] = "(", + closer: Union[str, ParserElement] = ")", + content: typing.Optional[ParserElement] = None, + ignore_expr: ParserElement = quoted_string(), + *, + ignoreExpr: ParserElement = quoted_string(), +) -> ParserElement: + """Helper method for defining nested lists enclosed in opening and + closing delimiters (``"("`` and ``")"`` are the default). + + Parameters: + - ``opener`` - opening character for a nested list + (default= ``"("``); can also be a pyparsing expression + - ``closer`` - closing character for a nested list + (default= ``")"``); can also be a pyparsing expression + - ``content`` - expression for items within the nested lists + (default= ``None``) + - ``ignore_expr`` - expression for ignoring opening and closing delimiters + (default= :class:`quoted_string`) + - ``ignoreExpr`` - this pre-PEP8 argument is retained for compatibility + but will be removed in a future release + + If an expression is not provided for the content argument, the + nested expression will capture all whitespace-delimited content + between delimiters as a list of separate values. + + Use the ``ignore_expr`` argument to define expressions that may + contain opening or closing characters that should not be treated as + opening or closing characters for nesting, such as quoted_string or + a comment expression. Specify multiple expressions using an + :class:`Or` or :class:`MatchFirst`. The default is + :class:`quoted_string`, but if no expressions are to be ignored, then + pass ``None`` for this argument. + + Example:: + + data_type = one_of("void int short long char float double") + decl_data_type = Combine(data_type + Opt(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR, RPAR = map(Suppress, "()") + + code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Opt(delimited_list(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(c_style_comment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.search_string(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + + prints:: + + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if ignoreExpr != ignore_expr: + ignoreExpr = ignore_expr if ignoreExpr == quoted_string() else ignoreExpr + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener, str_type) and isinstance(closer, str_type): + if len(opener) == 1 and len(closer) == 1: + if ignoreExpr is not None: + content = Combine( + OneOrMore( + ~ignoreExpr + + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS, + exact=1, + ) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = empty.copy() + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS + ).set_parse_action(lambda t: t[0].strip()) + else: + if ignoreExpr is not None: + content = Combine( + OneOrMore( + ~ignoreExpr + + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = Combine( + OneOrMore( + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + raise ValueError( + "opening and closing arguments must be strings if no content expression is given" + ) + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( + Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer) + ) + else: + ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer)) + ret.set_name("nested %s%s expression" % (opener, closer)) + return ret + + +def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">")): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr, str_type): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas, alphanums + "_-:") + if xml: + tagAttrValue = dbl_quoted_string.copy().set_parse_action(remove_quotes) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + else: + tagAttrValue = quoted_string.copy().set_parse_action(remove_quotes) | Word( + printables, exclude_chars=">" + ) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict( + ZeroOrMore( + Group( + tagAttrName.set_parse_action(lambda t: t[0].lower()) + + Opt(Suppress("=") + tagAttrValue) + ) + ) + ) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + closeTag = Combine(Literal("", adjacent=False) + + openTag.set_name("<%s>" % resname) + # add start results name in parse action now that ungrouped names are not reported at two levels + openTag.add_parse_action( + lambda t: t.__setitem__( + "start" + "".join(resname.replace(":", " ").title().split()), t.copy() + ) + ) + closeTag = closeTag( + "end" + "".join(resname.replace(":", " ").title().split()) + ).set_name("" % resname) + openTag.tag = resname + closeTag.tag = resname + openTag.tag_body = SkipTo(closeTag()) + return openTag, closeTag + + +def make_html_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """Helper to construct opening and closing tag expressions for HTML, + given a tag name. Matches tags in either upper or lower case, + attributes with namespaces and with quoted or unquoted values. + + Example:: + + text = 'More info at the pyparsing wiki page' + # make_html_tags returns pyparsing expressions for the opening and + # closing tags as a 2-tuple + a, a_end = make_html_tags("A") + link_expr = a + SkipTo(a_end)("link_text") + a_end + + for link in link_expr.search_string(text): + # attributes in the tag (like "href" shown here) are + # also accessible as named results + print(link.link_text, '->', link.href) + + prints:: + + pyparsing -> https://github.com/pyparsing/pyparsing/wiki + """ + return _makeTags(tag_str, False) + + +def make_xml_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """Helper to construct opening and closing tag expressions for XML, + given a tag name. Matches tags only in the given upper/lower case. + + Example: similar to :class:`make_html_tags` + """ + return _makeTags(tag_str, True) + + +any_open_tag: ParserElement +any_close_tag: ParserElement +any_open_tag, any_close_tag = make_html_tags( + Word(alphas, alphanums + "_:").set_name("any tag") +) + +_htmlEntityMap = {k.rstrip(";"): v for k, v in html.entities.html5.items()} +common_html_entity = Regex("&(?P" + "|".join(_htmlEntityMap) + ");").set_name( + "common HTML entity" +) + + +def replace_html_entity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + + +class OpAssoc(Enum): + LEFT = 1 + RIGHT = 2 + + +InfixNotationOperatorArgType = Union[ + ParserElement, str, Tuple[Union[ParserElement, str], Union[ParserElement, str]] +] +InfixNotationOperatorSpec = Union[ + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + typing.Optional[ParseAction], + ], + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + ], +] + + +def infix_notation( + base_expr: ParserElement, + op_list: List[InfixNotationOperatorSpec], + lpar: Union[str, ParserElement] = Suppress("("), + rpar: Union[str, ParserElement] = Suppress(")"), +) -> ParserElement: + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary + or binary, left- or right-associative. Parse actions can also be + attached to operator expressions. The generated parser will also + recognize the use of parentheses to override operator precedences + (see example below). + + Note: if you define a deep operator list, you may see performance + issues when using infix_notation. See + :class:`ParserElement.enable_packrat` for a mechanism to potentially + improve your parser performance. + + Parameters: + - ``base_expr`` - expression representing the most basic operand to + be used in the expression + - ``op_list`` - list of tuples, one for each operator precedence level + in the expression grammar; each tuple is of the form ``(op_expr, + num_operands, right_left_assoc, (optional)parse_action)``, where: + + - ``op_expr`` is the pyparsing expression for the operator; may also + be a string, which will be converted to a Literal; if ``num_operands`` + is 3, ``op_expr`` is a tuple of two expressions, for the two + operators separating the 3 terms + - ``num_operands`` is the number of terms for this operator (must be 1, + 2, or 3) + - ``right_left_assoc`` is the indicator whether the operator is right + or left associative, using the pyparsing-defined constants + ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``. + - ``parse_action`` is the parse action to be associated with + expressions matching this operator expression (the parse action + tuple member may be omitted); if the parse action is passed + a tuple or list of functions, this is equivalent to calling + ``set_parse_action(*fn)`` + (:class:`ParserElement.set_parse_action`) + - ``lpar`` - expression for matching left-parentheses; if passed as a + str, then will be parsed as Suppress(lpar). If lpar is passed as + an expression (such as ``Literal('(')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress('(')``) + - ``rpar`` - expression for matching right-parentheses; if passed as a + str, then will be parsed as Suppress(rpar). If rpar is passed as + an expression (such as ``Literal(')')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress(')')``) + + Example:: + + # simple example of four-function arithmetic with ints and + # variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infix_notation(integer | varname, + [ + ('-', 1, OpAssoc.RIGHT), + (one_of('* /'), 2, OpAssoc.LEFT), + (one_of('+ -'), 2, OpAssoc.LEFT), + ]) + + arith_expr.run_tests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', full_dump=False) + + prints:: + + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + # captive version of FollowedBy that does not do parse actions or capture results names + class _FB(FollowedBy): + def parseImpl(self, instring, loc, doActions=True): + self.expr.try_parse(instring, loc) + return loc, [] + + _FB.__name__ = "FollowedBy>" + + ret = Forward() + if isinstance(lpar, str): + lpar = Suppress(lpar) + if isinstance(rpar, str): + rpar = Suppress(rpar) + + # if lpar and rpar are not suppressed, wrap in group + if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)): + lastExpr = base_expr | Group(lpar + ret + rpar) + else: + lastExpr = base_expr | (lpar + ret + rpar) + + for i, operDef in enumerate(op_list): + opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] + if isinstance(opExpr, str_type): + opExpr = ParserElement._literalStringClass(opExpr) + if arity == 3: + if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2: + raise ValueError( + "if numterms=3, opExpr must be a tuple or list of two expressions" + ) + opExpr1, opExpr2 = opExpr + term_name = "{}{} term".format(opExpr1, opExpr2) + else: + term_name = "{} term".format(opExpr) + + if not 1 <= arity <= 3: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + + if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT): + raise ValueError("operator must indicate right or left associativity") + + thisExpr: Forward = Forward().set_name(term_name) + if rightLeftAssoc is OpAssoc.LEFT: + if arity == 1: + matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...]) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group( + lastExpr + (opExpr + lastExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr[2, ...]) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr + ) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr)) + elif rightLeftAssoc is OpAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Opt): + opExpr = Opt(opExpr) + matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group( + lastExpr + (opExpr + thisExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + thisExpr) + Group( + lastExpr + thisExpr[1, ...] + ) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr + ) + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.set_parse_action(*pa) + else: + matchExpr.set_parse_action(pa) + thisExpr <<= (matchExpr | lastExpr).setName(term_name) + lastExpr = thisExpr + ret <<= lastExpr + return ret + + +def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]): + """ + (DEPRECATED - use IndentedBlock class instead) + Helper method for defining space-delimited indentation blocks, + such as those used to define block statements in Python source code. + + Parameters: + + - ``blockStatementExpr`` - expression defining syntax of statement that + is repeated within the indented block + - ``indentStack`` - list created by caller to manage indentation stack + (multiple ``statementWithIndentedBlock`` expressions within a single + grammar should share a common ``indentStack``) + - ``indent`` - boolean indicating whether block must be indented beyond + the current level; set to ``False`` for block of left-most statements + (default= ``True``) + + A valid block must contain at least one ``blockStatement``. + + (Note that indentedBlock uses internal parse actions which make it + incompatible with packrat parsing.) + + Example:: + + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group("(" + Opt(delimitedList(identifier)) + ")") + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group(funcDecl + func_body) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << (funcDef | assignment | identifier) + + module_body = stmt[1, ...] + + parseTree = module_body.parseString(data) + parseTree.pprint() + + prints:: + + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + backup_stacks.append(indentStack[:]) + + def reset_stack(): + indentStack[:] = backup_stacks[-1] + + def checkPeerIndent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseException(s, l, "illegal nesting") + raise ParseException(s, l, "not a peer entry") + + def checkSubIndent(s, l, t): + curCol = col(l, s) + if curCol > indentStack[-1]: + indentStack.append(curCol) + else: + raise ParseException(s, l, "not a subentry") + + def checkUnindent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if not (indentStack and curCol in indentStack): + raise ParseException(s, l, "not an unindent") + if curCol < indentStack[-1]: + indentStack.pop() + + NL = OneOrMore(LineEnd().set_whitespace_chars("\t ").suppress()) + INDENT = (Empty() + Empty().set_parse_action(checkSubIndent)).set_name("INDENT") + PEER = Empty().set_parse_action(checkPeerIndent).set_name("") + UNDENT = Empty().set_parse_action(checkUnindent).set_name("UNINDENT") + if indent: + smExpr = Group( + Opt(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + UNDENT + ) + else: + smExpr = Group( + Opt(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + Opt(UNDENT) + ) + + # add a parse action to remove backup_stack from list of backups + smExpr.add_parse_action( + lambda: backup_stacks.pop(-1) and None if backup_stacks else None + ) + smExpr.set_fail_action(lambda a, b, c, d: reset_stack()) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.set_name("indented block") + + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +c_style_comment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/").set_name( + "C style comment" +) +"Comment of the form ``/* ... */``" + +html_comment = Regex(r"").set_name("HTML comment") +"Comment of the form ````" + +rest_of_line = Regex(r".*").leave_whitespace().set_name("rest of line") +dbl_slash_comment = Regex(r"//(?:\\\n|[^\n])*").set_name("// comment") +"Comment of the form ``// ... (to end of line)``" + +cpp_style_comment = Combine( + Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/" | dbl_slash_comment +).set_name("C++ style comment") +"Comment of either form :class:`c_style_comment` or :class:`dbl_slash_comment`" + +java_style_comment = cpp_style_comment +"Same as :class:`cpp_style_comment`" + +python_style_comment = Regex(r"#.*").set_name("Python style comment") +"Comment of the form ``# ... (to end of line)``" + + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs: List[ParserElement] = [ + v for v in vars().values() if isinstance(v, ParserElement) +] + + +# pre-PEP8 compatible names +delimitedList = delimited_list +countedArray = counted_array +matchPreviousLiteral = match_previous_literal +matchPreviousExpr = match_previous_expr +oneOf = one_of +dictOf = dict_of +originalTextFor = original_text_for +nestedExpr = nested_expr +makeHTMLTags = make_html_tags +makeXMLTags = make_xml_tags +anyOpenTag, anyCloseTag = any_open_tag, any_close_tag +commonHTMLEntity = common_html_entity +replaceHTMLEntity = replace_html_entity +opAssoc = OpAssoc +infixNotation = infix_notation +cStyleComment = c_style_comment +htmlComment = html_comment +restOfLine = rest_of_line +dblSlashComment = dbl_slash_comment +cppStyleComment = cpp_style_comment +javaStyleComment = java_style_comment +pythonStyleComment = python_style_comment diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/py.typed b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/results.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/results.py new file mode 100644 index 000000000..00c9421d3 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/results.py @@ -0,0 +1,760 @@ +# results.py +from collections.abc import MutableMapping, Mapping, MutableSequence, Iterator +import pprint +from weakref import ref as wkref +from typing import Tuple, Any + +str_type: Tuple[type, ...] = (str, bytes) +_generator_type = type((_ for _ in ())) + + +class _ParseResultsWithOffset: + __slots__ = ["tup"] + + def __init__(self, p1, p2): + self.tup = (p1, p2) + + def __getitem__(self, i): + return self.tup[i] + + def __getstate__(self): + return self.tup + + def __setstate__(self, *args): + self.tup = args[0] + + +class ParseResults: + """Structured parse results, to provide multiple means of access to + the parsed data: + + - as a list (``len(results)``) + - by list index (``results[0], results[1]``, etc.) + - by attribute (``results.`` - see :class:`ParserElement.set_results_name`) + + Example:: + + integer = Word(nums) + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + # equivalent form: + # date_str = (integer("year") + '/' + # + integer("month") + '/' + # + integer("day")) + + # parse_string returns a ParseResults object + result = date_str.parse_string("1999/12/31") + + def test(s, fn=repr): + print("{} -> {}".format(s, fn(eval(s)))) + test("list(result)") + test("result[0]") + test("result['month']") + test("result.day") + test("'month' in result") + test("'minutes' in result") + test("result.dump()", str) + + prints:: + + list(result) -> ['1999', '/', '12', '/', '31'] + result[0] -> '1999' + result['month'] -> '12' + result.day -> '31' + 'month' in result -> True + 'minutes' in result -> False + result.dump() -> ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + + _null_values: Tuple[Any, ...] = (None, [], "", ()) + + __slots__ = [ + "_name", + "_parent", + "_all_names", + "_modal", + "_toklist", + "_tokdict", + "__weakref__", + ] + + class List(list): + """ + Simple wrapper class to distinguish parsed list results that should be preserved + as actual Python lists, instead of being converted to :class:`ParseResults`: + + LBRACK, RBRACK = map(pp.Suppress, "[]") + element = pp.Forward() + item = ppc.integer + element_list = LBRACK + pp.delimited_list(element) + RBRACK + + # add parse actions to convert from ParseResults to actual Python collection types + def as_python_list(t): + return pp.ParseResults.List(t.as_list()) + element_list.add_parse_action(as_python_list) + + element <<= item | element_list + + element.run_tests(''' + 100 + [2,3,4] + [[2, 1],3,4] + [(2, 1),3,4] + (2,3,4) + ''', post_parse=lambda s, r: (r[0], type(r[0]))) + + prints: + + 100 + (100, ) + + [2,3,4] + ([2, 3, 4], ) + + [[2, 1],3,4] + ([[2, 1], 3, 4], ) + + (Used internally by :class:`Group` when `aslist=True`.) + """ + + def __new__(cls, contained=None): + if contained is None: + contained = [] + + if not isinstance(contained, list): + raise TypeError( + "{} may only be constructed with a list," + " not {}".format(cls.__name__, type(contained).__name__) + ) + + return list.__new__(cls) + + def __new__(cls, toklist=None, name=None, **kwargs): + if isinstance(toklist, ParseResults): + return toklist + self = object.__new__(cls) + self._name = None + self._parent = None + self._all_names = set() + + if toklist is None: + self._toklist = [] + elif isinstance(toklist, (list, _generator_type)): + self._toklist = ( + [toklist[:]] + if isinstance(toklist, ParseResults.List) + else list(toklist) + ) + else: + self._toklist = [toklist] + self._tokdict = dict() + return self + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance + ): + self._modal = modal + if name is not None and name != "": + if isinstance(name, int): + name = str(name) + if not modal: + self._all_names = {name} + self._name = name + if toklist not in self._null_values: + if isinstance(toklist, (str_type, type)): + toklist = [toklist] + if asList: + if isinstance(toklist, ParseResults): + self[name] = _ParseResultsWithOffset( + ParseResults(toklist._toklist), 0 + ) + else: + self[name] = _ParseResultsWithOffset( + ParseResults(toklist[0]), 0 + ) + self[name]._name = name + else: + try: + self[name] = toklist[0] + except (KeyError, TypeError, IndexError): + if toklist is not self: + self[name] = toklist + else: + self._name = name + + def __getitem__(self, i): + if isinstance(i, (int, slice)): + return self._toklist[i] + else: + if i not in self._all_names: + return self._tokdict[i][-1][0] + else: + return ParseResults([v[0] for v in self._tokdict[i]]) + + def __setitem__(self, k, v, isinstance=isinstance): + if isinstance(v, _ParseResultsWithOffset): + self._tokdict[k] = self._tokdict.get(k, list()) + [v] + sub = v[0] + elif isinstance(k, (int, slice)): + self._toklist[k] = v + sub = v + else: + self._tokdict[k] = self._tokdict.get(k, list()) + [ + _ParseResultsWithOffset(v, 0) + ] + sub = v + if isinstance(sub, ParseResults): + sub._parent = wkref(self) + + def __delitem__(self, i): + if isinstance(i, (int, slice)): + mylen = len(self._toklist) + del self._toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i + 1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name, occurrences in self._tokdict.items(): + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset( + value, position - (position > j) + ) + else: + del self._tokdict[i] + + def __contains__(self, k) -> bool: + return k in self._tokdict + + def __len__(self) -> int: + return len(self._toklist) + + def __bool__(self) -> bool: + return not not (self._toklist or self._tokdict) + + def __iter__(self) -> Iterator: + return iter(self._toklist) + + def __reversed__(self) -> Iterator: + return iter(self._toklist[::-1]) + + def keys(self): + return iter(self._tokdict) + + def values(self): + return (self[k] for k in self.keys()) + + def items(self): + return ((k, self[k]) for k in self.keys()) + + def haskeys(self) -> bool: + """ + Since ``keys()`` returns an iterator, this method is helpful in bypassing + code that looks for the existence of any defined results names.""" + return bool(self._tokdict) + + def pop(self, *args, **kwargs): + """ + Removes and returns item at specified index (default= ``last``). + Supports both ``list`` and ``dict`` semantics for ``pop()``. If + passed no argument or an integer argument, it will use ``list`` + semantics and pop tokens from the list of parsed tokens. If passed + a non-integer argument (most likely a string), it will use ``dict`` + semantics and pop the corresponding value from any defined results + names. A second default return value argument is supported, just as in + ``dict.pop()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + def remove_first(tokens): + tokens.pop(0) + numlist.add_parse_action(remove_first) + print(numlist.parse_string("0 123 321")) # -> ['123', '321'] + + label = Word(alphas) + patt = label("LABEL") + Word(nums)[1, ...] + print(patt.parse_string("AAB 123 321").dump()) + + # Use pop() in a parse action to remove named result (note that corresponding value is not + # removed from list form of results) + def remove_LABEL(tokens): + tokens.pop("LABEL") + return tokens + patt.add_parse_action(remove_LABEL) + print(patt.parse_string("AAB 123 321").dump()) + + prints:: + + ['AAB', '123', '321'] + - LABEL: 'AAB' + + ['AAB', '123', '321'] + """ + if not args: + args = [-1] + for k, v in kwargs.items(): + if k == "default": + args = (args[0], v) + else: + raise TypeError( + "pop() got an unexpected keyword argument {!r}".format(k) + ) + if isinstance(args[0], int) or len(args) == 1 or args[0] in self: + index = args[0] + ret = self[index] + del self[index] + return ret + else: + defaultvalue = args[1] + return defaultvalue + + def get(self, key, default_value=None): + """ + Returns named result matching the given key, or if there is no + such name, then returns the given ``default_value`` or ``None`` if no + ``default_value`` is specified. + + Similar to ``dict.get()``. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string("1999/12/31") + print(result.get("year")) # -> '1999' + print(result.get("hour", "not specified")) # -> 'not specified' + print(result.get("hour")) # -> None + """ + if key in self: + return self[key] + else: + return default_value + + def insert(self, index, ins_string): + """ + Inserts new element at location index in the list of parsed tokens. + + Similar to ``list.insert()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to insert the parse location in the front of the parsed results + def insert_locn(locn, tokens): + tokens.insert(0, locn) + numlist.add_parse_action(insert_locn) + print(numlist.parse_string("0 123 321")) # -> [0, '0', '123', '321'] + """ + self._toklist.insert(index, ins_string) + # fixup indices in token dictionary + for name, occurrences in self._tokdict.items(): + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset( + value, position + (position > index) + ) + + def append(self, item): + """ + Add single element to end of ``ParseResults`` list of elements. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to compute the sum of the parsed integers, and add it to the end + def append_sum(tokens): + tokens.append(sum(map(int, tokens))) + numlist.add_parse_action(append_sum) + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321', 444] + """ + self._toklist.append(item) + + def extend(self, itemseq): + """ + Add sequence of elements to end of ``ParseResults`` list of elements. + + Example:: + + patt = Word(alphas)[1, ...] + + # use a parse action to append the reverse of the matched strings, to make a palindrome + def make_palindrome(tokens): + tokens.extend(reversed([t[::-1] for t in tokens])) + return ''.join(tokens) + patt.add_parse_action(make_palindrome) + print(patt.parse_string("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' + """ + if isinstance(itemseq, ParseResults): + self.__iadd__(itemseq) + else: + self._toklist.extend(itemseq) + + def clear(self): + """ + Clear all elements and results names. + """ + del self._toklist[:] + self._tokdict.clear() + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + if name.startswith("__"): + raise AttributeError(name) + return "" + + def __add__(self, other) -> "ParseResults": + ret = self.copy() + ret += other + return ret + + def __iadd__(self, other) -> "ParseResults": + if other._tokdict: + offset = len(self._toklist) + addoffset = lambda a: offset if a < 0 else a + offset + otheritems = other._tokdict.items() + otherdictitems = [ + (k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) + for k, vlist in otheritems + for v in vlist + ] + for k, v in otherdictitems: + self[k] = v + if isinstance(v[0], ParseResults): + v[0]._parent = wkref(self) + + self._toklist += other._toklist + self._all_names |= other._all_names + return self + + def __radd__(self, other) -> "ParseResults": + if isinstance(other, int) and other == 0: + # useful for merging many ParseResults using sum() builtin + return self.copy() + else: + # this may raise a TypeError - so be it + return other + self + + def __repr__(self) -> str: + return "{}({!r}, {})".format(type(self).__name__, self._toklist, self.as_dict()) + + def __str__(self) -> str: + return ( + "[" + + ", ".join( + [ + str(i) if isinstance(i, ParseResults) else repr(i) + for i in self._toklist + ] + ) + + "]" + ) + + def _asStringList(self, sep=""): + out = [] + for item in self._toklist: + if out and sep: + out.append(sep) + if isinstance(item, ParseResults): + out += item._asStringList() + else: + out.append(str(item)) + return out + + def as_list(self) -> list: + """ + Returns the parse results as a nested list of matching tokens, all converted to strings. + + Example:: + + patt = Word(alphas)[1, ...] + result = patt.parse_string("sldkj lsdkj sldkj") + # even though the result prints in string-like form, it is actually a pyparsing ParseResults + print(type(result), result) # -> ['sldkj', 'lsdkj', 'sldkj'] + + # Use as_list() to create an actual list + result_list = result.as_list() + print(type(result_list), result_list) # -> ['sldkj', 'lsdkj', 'sldkj'] + """ + return [ + res.as_list() if isinstance(res, ParseResults) else res + for res in self._toklist + ] + + def as_dict(self) -> dict: + """ + Returns the named parse results as a nested dictionary. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('12/31/1999') + print(type(result), repr(result)) # -> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) + + result_dict = result.as_dict() + print(type(result_dict), repr(result_dict)) # -> {'day': '1999', 'year': '12', 'month': '31'} + + # even though a ParseResults supports dict-like access, sometime you just need to have a dict + import json + print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable + print(json.dumps(result.as_dict())) # -> {"month": "31", "day": "1999", "year": "12"} + """ + + def to_item(obj): + if isinstance(obj, ParseResults): + return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj] + else: + return obj + + return dict((k, to_item(v)) for k, v in self.items()) + + def copy(self) -> "ParseResults": + """ + Returns a new copy of a :class:`ParseResults` object. + """ + ret = ParseResults(self._toklist) + ret._tokdict = self._tokdict.copy() + ret._parent = self._parent + ret._all_names |= self._all_names + ret._name = self._name + return ret + + def get_name(self): + r""" + Returns the results name for this token expression. Useful when several + different expressions might match at a particular location. + + Example:: + + integer = Word(nums) + ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") + house_number_expr = Suppress('#') + Word(nums, alphanums) + user_data = (Group(house_number_expr)("house_number") + | Group(ssn_expr)("ssn") + | Group(integer)("age")) + user_info = user_data[1, ...] + + result = user_info.parse_string("22 111-22-3333 #221B") + for item in result: + print(item.get_name(), ':', item[0]) + + prints:: + + age : 22 + ssn : 111-22-3333 + house_number : 221B + """ + if self._name: + return self._name + elif self._parent: + par = self._parent() + + def find_in_parent(sub): + return next( + ( + k + for k, vlist in par._tokdict.items() + for v, loc in vlist + if sub is v + ), + None, + ) + + return find_in_parent(self) if par else None + elif ( + len(self) == 1 + and len(self._tokdict) == 1 + and next(iter(self._tokdict.values()))[0][1] in (0, -1) + ): + return next(iter(self._tokdict.keys())) + else: + return None + + def dump(self, indent="", full=True, include_list=True, _depth=0) -> str: + """ + Diagnostic method for listing out the contents of + a :class:`ParseResults`. Accepts an optional ``indent`` argument so + that this string can be embedded in a nested display of other data. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('1999/12/31') + print(result.dump()) + + prints:: + + ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + out = [] + NL = "\n" + out.append(indent + str(self.as_list()) if include_list else "") + + if full: + if self.haskeys(): + items = sorted((str(k), v) for k, v in self.items()) + for k, v in items: + if out: + out.append(NL) + out.append("{}{}- {}: ".format(indent, (" " * _depth), k)) + if isinstance(v, ParseResults): + if v: + out.append( + v.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ) + ) + else: + out.append(str(v)) + else: + out.append(repr(v)) + if any(isinstance(vv, ParseResults) for vv in self): + v = self + for i, vv in enumerate(v): + if isinstance(vv, ParseResults): + out.append( + "\n{}{}[{}]:\n{}{}{}".format( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + vv.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ), + ) + ) + else: + out.append( + "\n%s%s[%d]:\n%s%s%s" + % ( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + str(vv), + ) + ) + + return "".join(out) + + def pprint(self, *args, **kwargs): + """ + Pretty-printer for parsed results as a list, using the + `pprint `_ module. + Accepts additional positional or keyword args as defined for + `pprint.pprint `_ . + + Example:: + + ident = Word(alphas, alphanums) + num = Word(nums) + func = Forward() + term = ident | num | Group('(' + func + ')') + func <<= ident + Group(Optional(delimited_list(term))) + result = func.parse_string("fna a,b,(fnb c,d,200),100") + result.pprint(width=40) + + prints:: + + ['fna', + ['a', + 'b', + ['(', 'fnb', ['c', 'd', '200'], ')'], + '100']] + """ + pprint.pprint(self.as_list(), *args, **kwargs) + + # add support for pickle protocol + def __getstate__(self): + return ( + self._toklist, + ( + self._tokdict.copy(), + self._parent is not None and self._parent() or None, + self._all_names, + self._name, + ), + ) + + def __setstate__(self, state): + self._toklist, (self._tokdict, par, inAccumNames, self._name) = state + self._all_names = set(inAccumNames) + if par is not None: + self._parent = wkref(par) + else: + self._parent = None + + def __getnewargs__(self): + return self._toklist, self._name + + def __dir__(self): + return dir(type(self)) + list(self.keys()) + + @classmethod + def from_dict(cls, other, name=None) -> "ParseResults": + """ + Helper classmethod to construct a ``ParseResults`` from a ``dict``, preserving the + name-value relations as results names. If an optional ``name`` argument is + given, a nested ``ParseResults`` will be returned. + """ + + def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + else: + return not isinstance(obj, str_type) + + ret = cls([]) + for k, v in other.items(): + if isinstance(v, Mapping): + ret += cls.from_dict(v, name=k) + else: + ret += cls([v], name=k, asList=is_iterable(v)) + if name is not None: + ret = cls([ret], name=name) + return ret + + asList = as_list + asDict = as_dict + getName = get_name + + +MutableMapping.register(ParseResults) +MutableSequence.register(ParseResults) diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/testing.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/testing.py new file mode 100644 index 000000000..84a0ef170 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/testing.py @@ -0,0 +1,331 @@ +# testing.py + +from contextlib import contextmanager +import typing + +from .core import ( + ParserElement, + ParseException, + Keyword, + __diag__, + __compat__, +) + + +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - bounded recursion parsing + - default whitespace characters. + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example:: + + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + + def __init__(self): + self._save_context = {} + + def save(self): + self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS + + self._save_context[ + "literal_string_class" + ] = ParserElement._literalStringClass + + self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace + + self._save_context["packrat_enabled"] = ParserElement._packratEnabled + if ParserElement._packratEnabled: + self._save_context[ + "packrat_cache_size" + ] = ParserElement.packrat_cache.size + else: + self._save_context["packrat_cache_size"] = None + self._save_context["packrat_parse"] = ParserElement._parse + self._save_context[ + "recursion_enabled" + ] = ParserElement._left_recursion_enabled + + self._save_context["__diag__"] = { + name: getattr(__diag__, name) for name in __diag__._all_names + } + + self._save_context["__compat__"] = { + "collect_all_And_tokens": __compat__.collect_all_And_tokens + } + + return self + + def restore(self): + # reset pyparsing global state + if ( + ParserElement.DEFAULT_WHITE_CHARS + != self._save_context["default_whitespace"] + ): + ParserElement.set_default_whitespace_chars( + self._save_context["default_whitespace"] + ) + + ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"] + + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] + ParserElement.inlineLiteralsUsing( + self._save_context["literal_string_class"] + ) + + for name, value in self._save_context["__diag__"].items(): + (__diag__.enable if value else __diag__.disable)(name) + + ParserElement._packratEnabled = False + if self._save_context["packrat_enabled"]: + ParserElement.enable_packrat(self._save_context["packrat_cache_size"]) + else: + ParserElement._parse = self._save_context["packrat_parse"] + ParserElement._left_recursion_enabled = self._save_context[ + "recursion_enabled" + ] + + __compat__.collect_all_And_tokens = self._save_context["__compat__"] + + return self + + def copy(self): + ret = type(self)() + ret._save_context.update(self._save_context) + return ret + + def __enter__(self): + return self.save() + + def __exit__(self, *args): + self.restore() + + class TestParseResultsAsserts: + """ + A mixin class to add parse results assertion methods to normal unittest.TestCase classes. + """ + + def assertParseResultsEquals( + self, result, expected_list=None, expected_dict=None, msg=None + ): + """ + Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``, + and compare any defined results names with an optional ``expected_dict``. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.as_list(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.as_dict(), msg=msg) + + def assertParseAndCheckList( + self, expr, test_string, expected_list, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asList()`` is equal to the ``expected_list``. + """ + result = expr.parse_string(test_string, parse_all=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict( + self, expr, test_string, expected_dict, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``. + """ + result = expr.parse_string(test_string, parseAll=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults( + self, run_tests_report, expected_parse_results=None, msg=None + ): + """ + Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of + list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped + with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``. + Finally, asserts that the overall ``runTests()`` success value is ``True``. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [ + (*rpt, expected) + for rpt, expected in zip(run_test_results, expected_parse_results) + ] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next( + (exp for exp in expected if isinstance(exp, str)), None + ) + expected_exception = next( + ( + exp + for exp in expected + if isinstance(exp, type) and issubclass(exp, Exception) + ), + None, + ) + if expected_exception is not None: + with self.assertRaises( + expected_exception=expected_exception, msg=fail_msg or msg + ): + if isinstance(result, Exception): + raise result + else: + expected_list = next( + (exp for exp in expected if isinstance(exp, list)), None + ) + expected_dict = next( + (exp for exp in expected if isinstance(exp, dict)), None + ) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals( + result, + expected_list=expected_list, + expected_dict=expected_dict, + msg=fail_msg or msg, + ) + else: + # warning here maybe? + print("no validation for {!r}".format(test_string)) + + # do this last, in case some specific test results can be reported instead + self.assertTrue( + run_test_success, msg=msg if msg is not None else "failed runTests" + ) + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield + + @staticmethod + def with_line_numbers( + s: str, + start_line: typing.Optional[int] = None, + end_line: typing.Optional[int] = None, + expand_tabs: bool = True, + eol_mark: str = "|", + mark_spaces: typing.Optional[str] = None, + mark_control: typing.Optional[str] = None, + ) -> str: + """ + Helpful method for debugging a parser - prints a string with line and column numbers. + (Line and column numbers are 1-based.) + + :param s: tuple(bool, str - string to be printed with line and column numbers + :param start_line: int - (optional) starting line number in s to print (default=1) + :param end_line: int - (optional) ending line number in s to print (default=len(s)) + :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default + :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|") + :param mark_spaces: str - (optional) special character to display in place of spaces + :param mark_control: str - (optional) convert non-printing control characters to a placeholding + character; valid values: + - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊" + - any single character string - replace control characters with given string + - None (default) - string is displayed as-is + + :return: str - input string with leading line numbers and column number headers + """ + if expand_tabs: + s = s.expandtabs() + if mark_control is not None: + if mark_control == "unicode": + tbl = str.maketrans( + {c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))} + | {127: 0x2421} + ) + eol_mark = "" + else: + tbl = str.maketrans( + {c: mark_control for c in list(range(0, 32)) + [127]} + ) + s = s.translate(tbl) + if mark_spaces is not None and mark_spaces != " ": + if mark_spaces == "unicode": + tbl = str.maketrans({9: 0x2409, 32: 0x2423}) + s = s.translate(tbl) + else: + s = s.replace(" ", mark_spaces) + if start_line is None: + start_line = 1 + if end_line is None: + end_line = len(s) + end_line = min(end_line, len(s)) + start_line = min(max(1, start_line), end_line) + + if mark_control != "unicode": + s_lines = s.splitlines()[start_line - 1 : end_line] + else: + s_lines = [line + "␊" for line in s.split("␊")[start_line - 1 : end_line]] + if not s_lines: + return "" + + lineno_width = len(str(end_line)) + max_line_len = max(len(line) for line in s_lines) + lead = " " * (lineno_width + 1) + if max_line_len >= 99: + header0 = ( + lead + + "".join( + "{}{}".format(" " * 99, (i + 1) % 100) + for i in range(max(max_line_len // 100, 1)) + ) + + "\n" + ) + else: + header0 = "" + header1 = ( + header0 + + lead + + "".join( + " {}".format((i + 1) % 10) + for i in range(-(-max_line_len // 10)) + ) + + "\n" + ) + header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n" + return ( + header1 + + header2 + + "\n".join( + "{:{}d}:{}{}".format(i, lineno_width, line, eol_mark) + for i, line in enumerate(s_lines, start=start_line) + ) + + "\n" + ) diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/unicode.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/unicode.py new file mode 100644 index 000000000..065262039 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/unicode.py @@ -0,0 +1,352 @@ +# unicode.py + +import sys +from itertools import filterfalse +from typing import List, Tuple, Union + + +class _lazyclassproperty: + def __init__(self, fn): + self.fn = fn + self.__doc__ = fn.__doc__ + self.__name__ = fn.__name__ + + def __get__(self, obj, cls): + if cls is None: + cls = type(obj) + if not hasattr(cls, "_intern") or any( + cls._intern is getattr(superclass, "_intern", []) + for superclass in cls.__mro__[1:] + ): + cls._intern = {} + attrname = self.fn.__name__ + if attrname not in cls._intern: + cls._intern[attrname] = self.fn(cls) + return cls._intern[attrname] + + +UnicodeRangeList = List[Union[Tuple[int, int], Tuple[int]]] + + +class unicode_set: + """ + A set of Unicode characters, for language-specific strings for + ``alphas``, ``nums``, ``alphanums``, and ``printables``. + A unicode_set is defined by a list of ranges in the Unicode character + set, in a class attribute ``_ranges``. Ranges can be specified using + 2-tuples or a 1-tuple, such as:: + + _ranges = [ + (0x0020, 0x007e), + (0x00a0, 0x00ff), + (0x0100,), + ] + + Ranges are left- and right-inclusive. A 1-tuple of (x,) is treated as (x, x). + + A unicode set can also be defined using multiple inheritance of other unicode sets:: + + class CJK(Chinese, Japanese, Korean): + pass + """ + + _ranges: UnicodeRangeList = [] + + @_lazyclassproperty + def _chars_for_ranges(cls): + ret = [] + for cc in cls.__mro__: + if cc is unicode_set: + break + for rr in getattr(cc, "_ranges", ()): + ret.extend(range(rr[0], rr[-1] + 1)) + return [chr(c) for c in sorted(set(ret))] + + @_lazyclassproperty + def printables(cls): + "all non-whitespace characters in this range" + return "".join(filterfalse(str.isspace, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphas(cls): + "all alphabetic characters in this range" + return "".join(filter(str.isalpha, cls._chars_for_ranges)) + + @_lazyclassproperty + def nums(cls): + "all numeric digit characters in this range" + return "".join(filter(str.isdigit, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphanums(cls): + "all alphanumeric characters in this range" + return cls.alphas + cls.nums + + @_lazyclassproperty + def identchars(cls): + "all characters in this range that are valid identifier characters, plus underscore '_'" + return "".join( + sorted( + set( + "".join(filter(str.isidentifier, cls._chars_for_ranges)) + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzªµº" + + "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ" + + "_" + ) + ) + ) + + @_lazyclassproperty + def identbodychars(cls): + """ + all characters in this range that are valid identifier body characters, + plus the digits 0-9 + """ + return "".join( + sorted( + set( + cls.identchars + + "0123456789" + + "".join( + [c for c in cls._chars_for_ranges if ("_" + c).isidentifier()] + ) + ) + ) + ) + + +class pyparsing_unicode(unicode_set): + """ + A namespace class for defining common language unicode_sets. + """ + + # fmt: off + + # define ranges in language character sets + _ranges: UnicodeRangeList = [ + (0x0020, sys.maxunicode), + ] + + class BasicMultilingualPlane(unicode_set): + "Unicode set for the Basic Multilingual Plane" + _ranges: UnicodeRangeList = [ + (0x0020, 0xFFFF), + ] + + class Latin1(unicode_set): + "Unicode set for Latin-1 Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0020, 0x007E), + (0x00A0, 0x00FF), + ] + + class LatinA(unicode_set): + "Unicode set for Latin-A Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0100, 0x017F), + ] + + class LatinB(unicode_set): + "Unicode set for Latin-B Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0180, 0x024F), + ] + + class Greek(unicode_set): + "Unicode set for Greek Unicode Character Ranges" + _ranges: UnicodeRangeList = [ + (0x0342, 0x0345), + (0x0370, 0x0377), + (0x037A, 0x037F), + (0x0384, 0x038A), + (0x038C,), + (0x038E, 0x03A1), + (0x03A3, 0x03E1), + (0x03F0, 0x03FF), + (0x1D26, 0x1D2A), + (0x1D5E,), + (0x1D60,), + (0x1D66, 0x1D6A), + (0x1F00, 0x1F15), + (0x1F18, 0x1F1D), + (0x1F20, 0x1F45), + (0x1F48, 0x1F4D), + (0x1F50, 0x1F57), + (0x1F59,), + (0x1F5B,), + (0x1F5D,), + (0x1F5F, 0x1F7D), + (0x1F80, 0x1FB4), + (0x1FB6, 0x1FC4), + (0x1FC6, 0x1FD3), + (0x1FD6, 0x1FDB), + (0x1FDD, 0x1FEF), + (0x1FF2, 0x1FF4), + (0x1FF6, 0x1FFE), + (0x2129,), + (0x2719, 0x271A), + (0xAB65,), + (0x10140, 0x1018D), + (0x101A0,), + (0x1D200, 0x1D245), + (0x1F7A1, 0x1F7A7), + ] + + class Cyrillic(unicode_set): + "Unicode set for Cyrillic Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0400, 0x052F), + (0x1C80, 0x1C88), + (0x1D2B,), + (0x1D78,), + (0x2DE0, 0x2DFF), + (0xA640, 0xA672), + (0xA674, 0xA69F), + (0xFE2E, 0xFE2F), + ] + + class Chinese(unicode_set): + "Unicode set for Chinese Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x2E80, 0x2E99), + (0x2E9B, 0x2EF3), + (0x31C0, 0x31E3), + (0x3400, 0x4DB5), + (0x4E00, 0x9FEF), + (0xA700, 0xA707), + (0xF900, 0xFA6D), + (0xFA70, 0xFAD9), + (0x16FE2, 0x16FE3), + (0x1F210, 0x1F212), + (0x1F214, 0x1F23B), + (0x1F240, 0x1F248), + (0x20000, 0x2A6D6), + (0x2A700, 0x2B734), + (0x2B740, 0x2B81D), + (0x2B820, 0x2CEA1), + (0x2CEB0, 0x2EBE0), + (0x2F800, 0x2FA1D), + ] + + class Japanese(unicode_set): + "Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges" + _ranges: UnicodeRangeList = [] + + class Kanji(unicode_set): + "Unicode set for Kanji Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x4E00, 0x9FBF), + (0x3000, 0x303F), + ] + + class Hiragana(unicode_set): + "Unicode set for Hiragana Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x3041, 0x3096), + (0x3099, 0x30A0), + (0x30FC,), + (0xFF70,), + (0x1B001,), + (0x1B150, 0x1B152), + (0x1F200,), + ] + + class Katakana(unicode_set): + "Unicode set for Katakana Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x3099, 0x309C), + (0x30A0, 0x30FF), + (0x31F0, 0x31FF), + (0x32D0, 0x32FE), + (0xFF65, 0xFF9F), + (0x1B000,), + (0x1B164, 0x1B167), + (0x1F201, 0x1F202), + (0x1F213,), + ] + + class Hangul(unicode_set): + "Unicode set for Hangul (Korean) Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x1100, 0x11FF), + (0x302E, 0x302F), + (0x3131, 0x318E), + (0x3200, 0x321C), + (0x3260, 0x327B), + (0x327E,), + (0xA960, 0xA97C), + (0xAC00, 0xD7A3), + (0xD7B0, 0xD7C6), + (0xD7CB, 0xD7FB), + (0xFFA0, 0xFFBE), + (0xFFC2, 0xFFC7), + (0xFFCA, 0xFFCF), + (0xFFD2, 0xFFD7), + (0xFFDA, 0xFFDC), + ] + + Korean = Hangul + + class CJK(Chinese, Japanese, Hangul): + "Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range" + + class Thai(unicode_set): + "Unicode set for Thai Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0E01, 0x0E3A), + (0x0E3F, 0x0E5B) + ] + + class Arabic(unicode_set): + "Unicode set for Arabic Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0600, 0x061B), + (0x061E, 0x06FF), + (0x0700, 0x077F), + ] + + class Hebrew(unicode_set): + "Unicode set for Hebrew Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0591, 0x05C7), + (0x05D0, 0x05EA), + (0x05EF, 0x05F4), + (0xFB1D, 0xFB36), + (0xFB38, 0xFB3C), + (0xFB3E,), + (0xFB40, 0xFB41), + (0xFB43, 0xFB44), + (0xFB46, 0xFB4F), + ] + + class Devanagari(unicode_set): + "Unicode set for Devanagari Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x0900, 0x097F), + (0xA8E0, 0xA8FF) + ] + + # fmt: on + + +pyparsing_unicode.Japanese._ranges = ( + pyparsing_unicode.Japanese.Kanji._ranges + + pyparsing_unicode.Japanese.Hiragana._ranges + + pyparsing_unicode.Japanese.Katakana._ranges +) + +pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane + +# add language identifiers using language Unicode +pyparsing_unicode.العربية = pyparsing_unicode.Arabic +pyparsing_unicode.中文 = pyparsing_unicode.Chinese +pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic +pyparsing_unicode.Ελληνικά = pyparsing_unicode.Greek +pyparsing_unicode.עִברִית = pyparsing_unicode.Hebrew +pyparsing_unicode.日本語 = pyparsing_unicode.Japanese +pyparsing_unicode.Japanese.漢字 = pyparsing_unicode.Japanese.Kanji +pyparsing_unicode.Japanese.カタカナ = pyparsing_unicode.Japanese.Katakana +pyparsing_unicode.Japanese.ひらがな = pyparsing_unicode.Japanese.Hiragana +pyparsing_unicode.한국어 = pyparsing_unicode.Korean +pyparsing_unicode.ไทย = pyparsing_unicode.Thai +pyparsing_unicode.देवनागरी = pyparsing_unicode.Devanagari diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyparsing/util.py b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/util.py new file mode 100644 index 000000000..34ce092c6 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/pyparsing/util.py @@ -0,0 +1,235 @@ +# util.py +import warnings +import types +import collections +import itertools +from functools import lru_cache +from typing import List, Union, Iterable + +_bslash = chr(92) + + +class __config_flags: + """Internal class for defining compatibility and debugging flags""" + + _all_names: List[str] = [] + _fixed_names: List[str] = [] + _type_desc = "configuration" + + @classmethod + def _set(cls, dname, value): + if dname in cls._fixed_names: + warnings.warn( + "{}.{} {} is {} and cannot be overridden".format( + cls.__name__, + dname, + cls._type_desc, + str(getattr(cls, dname)).upper(), + ) + ) + return + if dname in cls._all_names: + setattr(cls, dname, value) + else: + raise ValueError("no such {} {!r}".format(cls._type_desc, dname)) + + enable = classmethod(lambda cls, name: cls._set(name, True)) + disable = classmethod(lambda cls, name: cls._set(name, False)) + + +@lru_cache(maxsize=128) +def col(loc: int, strg: str) -> int: + """ + Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See + :class:`ParserElement.parseString` for more + information on parsing strings containing ```` s, and suggested + methods to maintain a consistent view of the parsed string, the parse + location, and line and column positions within the parsed string. + """ + s = strg + return 1 if 0 < loc < len(s) and s[loc - 1] == "\n" else loc - s.rfind("\n", 0, loc) + + +@lru_cache(maxsize=128) +def lineno(loc: int, strg: str) -> int: + """Returns current line number within a string, counting newlines as line separators. + The first line is number 1. + + Note - the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See :class:`ParserElement.parseString` + for more information on parsing strings containing ```` s, and + suggested methods to maintain a consistent view of the parsed string, the + parse location, and line and column positions within the parsed string. + """ + return strg.count("\n", 0, loc) + 1 + + +@lru_cache(maxsize=128) +def line(loc: int, strg: str) -> str: + """ + Returns the line of text containing loc within a string, counting newlines as line separators. + """ + last_cr = strg.rfind("\n", 0, loc) + next_cr = strg.find("\n", loc) + return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :] + + +class _UnboundedCache: + def __init__(self): + cache = {} + cache_get = cache.get + self.not_in_cache = not_in_cache = object() + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + + def clear(_): + cache.clear() + + self.size = None + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class _FifoCache: + def __init__(self, size): + self.not_in_cache = not_in_cache = object() + cache = collections.OrderedDict() + cache_get = cache.get + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + while len(cache) > size: + cache.popitem(last=False) + + def clear(_): + cache.clear() + + self.size = size + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class LRUMemo: + """ + A memoizing mapping that retains `capacity` deleted items + + The memo tracks retained items by their access order; once `capacity` items + are retained, the least recently used item is discarded. + """ + + def __init__(self, capacity): + self._capacity = capacity + self._active = {} + self._memory = collections.OrderedDict() + + def __getitem__(self, key): + try: + return self._active[key] + except KeyError: + self._memory.move_to_end(key) + return self._memory[key] + + def __setitem__(self, key, value): + self._memory.pop(key, None) + self._active[key] = value + + def __delitem__(self, key): + try: + value = self._active.pop(key) + except KeyError: + pass + else: + while len(self._memory) >= self._capacity: + self._memory.popitem(last=False) + self._memory[key] = value + + def clear(self): + self._active.clear() + self._memory.clear() + + +class UnboundedMemo(dict): + """ + A memoizing mapping that retains all deleted items + """ + + def __delitem__(self, key): + pass + + +def _escape_regex_range_chars(s: str) -> str: + # escape these chars: ^-[] + for c in r"\^-[]": + s = s.replace(c, _bslash + c) + s = s.replace("\n", r"\n") + s = s.replace("\t", r"\t") + return str(s) + + +def _collapse_string_to_ranges( + s: Union[str, Iterable[str]], re_escape: bool = True +) -> str: + def is_consecutive(c): + c_int = ord(c) + is_consecutive.prev, prev = c_int, is_consecutive.prev + if c_int - prev > 1: + is_consecutive.value = next(is_consecutive.counter) + return is_consecutive.value + + is_consecutive.prev = 0 + is_consecutive.counter = itertools.count() + is_consecutive.value = -1 + + def escape_re_range_char(c): + return "\\" + c if c in r"\^-][" else c + + def no_escape_re_range_char(c): + return c + + if not re_escape: + escape_re_range_char = no_escape_re_range_char + + ret = [] + s = "".join(sorted(set(s))) + if len(s) > 3: + for _, chars in itertools.groupby(s, key=is_consecutive): + first = last = next(chars) + last = collections.deque( + itertools.chain(iter([last]), chars), maxlen=1 + ).pop() + if first == last: + ret.append(escape_re_range_char(first)) + else: + sep = "" if ord(last) == ord(first) + 1 else "-" + ret.append( + "{}{}{}".format( + escape_re_range_char(first), sep, escape_re_range_char(last) + ) + ) + else: + ret = [escape_re_range_char(c) for c in s] + + return "".join(ret) + + +def _flatten(ll: list) -> list: + ret = [] + for i in ll: + if isinstance(i, list): + ret.extend(_flatten(i)) + else: + ret.append(i) + return ret diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/LICENSE.mit b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/LICENSE.mit index 6609e4c05..6cbf251f6 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/LICENSE.mit +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/LICENSE.mit @@ -1,4 +1,4 @@ -Copyright (c) 2019 Tobias Gustafsson +Copyright (c) 2022 Tobias Gustafsson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_checked_types.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_checked_types.py index 293d989f1..8ab8c2a8c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_checked_types.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_checked_types.py @@ -1,7 +1,8 @@ -from ._compat import Iterable -import six +from enum import Enum + +from abc import abstractmethod, ABCMeta +from collections.abc import Iterable -from pyrsistent._compat import Enum, string_types from pyrsistent._pmap import PMap, pmap from pyrsistent._pset import PSet, pset from pyrsistent._pvector import PythonPVector, python_pvector @@ -14,9 +15,11 @@ class CheckedType(object): __slots__ = () @classmethod + @abstractmethod def create(cls, source_data, _factory_fields=None): raise NotImplementedError() + @abstractmethod def serialize(self, format=None): raise NotImplementedError() @@ -48,7 +51,7 @@ def __str__(self): _preserved_iterable_types = ( - Enum, + Enum, ) """Some types are themselves iterable, but we want to use the type itself and not its members for the type specification. This defines a set of such types @@ -69,7 +72,7 @@ def maybe_parse_user_type(t): """ is_type = isinstance(t, type) is_preserved = isinstance(t, type) and issubclass(t, _preserved_iterable_types) - is_string = isinstance(t, string_types) + is_string = isinstance(t, str) is_iterable = isinstance(t, Iterable) if is_preserved: @@ -159,7 +162,7 @@ def store_invariants(dct, bases, destination_name, source_name): dct[destination_name] = tuple(wrap_invariant(inv) for inv in invariants) -class _CheckedTypeMeta(type): +class _CheckedTypeMeta(ABCMeta): def __new__(mcs, name, bases, dct): _store_types(dct, bases, '_checked_types', '__type__') store_invariants(dct, bases, '_checked_invariants', '__invariant__') @@ -268,8 +271,7 @@ def _checked_type_create(cls, source_data, _factory_fields=None, ignore_extra=Fa return cls(source_data) -@six.add_metaclass(_CheckedTypeMeta) -class CheckedPVector(PythonPVector, CheckedType): +class CheckedPVector(PythonPVector, CheckedType, metaclass=_CheckedTypeMeta): """ A CheckedPVector is a PVector which allows specifying type and invariant checks. @@ -355,8 +357,7 @@ def evolver(self): return CheckedPVector.Evolver(self.__class__, self) -@six.add_metaclass(_CheckedTypeMeta) -class CheckedPSet(PSet, CheckedType): +class CheckedPSet(PSet, CheckedType, metaclass=_CheckedTypeMeta): """ A CheckedPSet is a PSet which allows specifying type and invariant checks. @@ -454,8 +455,7 @@ def default_serializer(self, _, key, value): _UNDEFINED_CHECKED_PMAP_SIZE = object() -@six.add_metaclass(_CheckedMapTypeMeta) -class CheckedPMap(PMap, CheckedType): +class CheckedPMap(PMap, CheckedType, metaclass=_CheckedMapTypeMeta): """ A CheckedPMap is a PMap which allows specifying type and invariant checks. diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_compat.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_compat.py deleted file mode 100644 index e728586af..000000000 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_compat.py +++ /dev/null @@ -1,31 +0,0 @@ -from six import string_types - - -# enum compat -try: - from enum import Enum -except: - class Enum(object): pass - # no objects will be instances of this class - -# collections compat -try: - from collections.abc import ( - Container, - Hashable, - Iterable, - Mapping, - Sequence, - Set, - Sized, - ) -except ImportError: - from collections import ( - Container, - Hashable, - Iterable, - Mapping, - Sequence, - Set, - Sized, - ) diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_field_common.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_field_common.py index ca1cccd43..508dd2f79 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_field_common.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_field_common.py @@ -1,6 +1,3 @@ -import six -import sys - from pyrsistent._checked_types import ( CheckedPMap, CheckedPSet, @@ -16,8 +13,6 @@ from pyrsistent._checked_types import wrap_invariant import inspect -PY2 = sys.version_info[0] < 3 - def set_fields(dct, bases, name): dct[name] = dict(sum([list(b.__dict__.get(name, {}).items()) for b in bases], [])) @@ -66,10 +61,7 @@ def is_field_ignore_extra_complaint(type_cls, field, ignore_extra): if not is_type_cls(type_cls, field.type): return False - if PY2: - return 'ignore_extra' in inspect.getargspec(field.factory).args - else: - return 'ignore_extra' in inspect.signature(field.factory).parameters + return 'ignore_extra' in inspect.signature(field.factory).parameters @@ -139,7 +131,7 @@ def field(type=PFIELD_NO_TYPE, invariant=PFIELD_NO_INVARIANT, initial=PFIELD_NO_ def _check_field_parameters(field): for t in field.type: - if not isinstance(t, type) and not isinstance(t, six.string_types): + if not isinstance(t, type) and not isinstance(t, str): raise TypeError('Type parameter expected, not {0}'.format(type(t))) if field.initial is not PFIELD_NO_INITIAL and \ @@ -192,7 +184,7 @@ def _types_to_names(types): """Convert a tuple of types to a human-readable string.""" return "".join(get_type(typ).__name__.capitalize() for typ in types) -def _make_seq_field_type(checked_class, item_type): +def _make_seq_field_type(checked_class, item_type, item_invariant): """Create a subclass of the given checked class with the given item type.""" type_ = _seq_field_types.get((checked_class, item_type)) if type_ is not None: @@ -200,6 +192,7 @@ def _make_seq_field_type(checked_class, item_type): class TheType(checked_class): __type__ = item_type + __invariant__ = item_invariant def __reduce__(self): return (_restore_seq_field_pickle, @@ -210,7 +203,9 @@ def __reduce__(self): _seq_field_types[checked_class, item_type] = TheType return TheType -def _sequence_field(checked_class, item_type, optional, initial): +def _sequence_field(checked_class, item_type, optional, initial, + invariant=PFIELD_NO_INVARIANT, + item_invariant=PFIELD_NO_INVARIANT): """ Create checked field for either ``PSet`` or ``PVector``. @@ -222,7 +217,7 @@ def _sequence_field(checked_class, item_type, optional, initial): :return: A ``field`` containing a checked class. """ - TheType = _make_seq_field_type(checked_class, item_type) + TheType = _make_seq_field_type(checked_class, item_type, item_invariant) if optional: def factory(argument, _factory_fields=None, ignore_extra=False): @@ -235,10 +230,13 @@ def factory(argument, _factory_fields=None, ignore_extra=False): return field(type=optional_type(TheType) if optional else TheType, factory=factory, mandatory=True, + invariant=invariant, initial=factory(initial)) -def pset_field(item_type, optional=False, initial=()): +def pset_field(item_type, optional=False, initial=(), + invariant=PFIELD_NO_INVARIANT, + item_invariant=PFIELD_NO_INVARIANT): """ Create checked ``PSet`` field. @@ -250,11 +248,14 @@ def pset_field(item_type, optional=False, initial=()): :return: A ``field`` containing a ``CheckedPSet`` of the given type. """ - return _sequence_field(CheckedPSet, item_type, optional, - initial) + return _sequence_field(CheckedPSet, item_type, optional, initial, + invariant=invariant, + item_invariant=item_invariant) -def pvector_field(item_type, optional=False, initial=()): +def pvector_field(item_type, optional=False, initial=(), + invariant=PFIELD_NO_INVARIANT, + item_invariant=PFIELD_NO_INVARIANT): """ Create checked ``PVector`` field. @@ -266,8 +267,9 @@ def pvector_field(item_type, optional=False, initial=()): :return: A ``field`` containing a ``CheckedPVector`` of the given type. """ - return _sequence_field(CheckedPVector, item_type, optional, - initial) + return _sequence_field(CheckedPVector, item_type, optional, initial, + invariant=invariant, + item_invariant=item_invariant) _valid = lambda item: (True, "") diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_helpers.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_helpers.py index c9c58feac..1320e6576 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_helpers.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_helpers.py @@ -1,11 +1,9 @@ from functools import wraps -import six from pyrsistent._pmap import PMap, pmap from pyrsistent._pset import PSet, pset from pyrsistent._pvector import PVector, pvector - -def freeze(o): +def freeze(o, strict=True): """ Recursively convert simple Python containers into pyrsistent versions of those containers. @@ -15,6 +13,11 @@ def freeze(o): - set is converted to pset, but not recursively - tuple is converted to tuple, recursively. + If strict == True (default): + + - freeze is called on elements of pvectors + - freeze is called on values of pmaps + Sets and dict keys are not recursively frozen because they do not contain mutable data by convention. The main exception to this rule is that dict keys and set elements are often instances of mutable objects that @@ -28,18 +31,21 @@ def freeze(o): (1, pvector([])) """ typ = type(o) - if typ is dict: - return pmap(dict((k, freeze(v)) for k, v in six.iteritems(o))) - if typ is list: - return pvector(map(freeze, o)) + if typ is dict or (strict and isinstance(o, PMap)): + return pmap({k: freeze(v, strict) for k, v in o.items()}) + if typ is list or (strict and isinstance(o, PVector)): + curried_freeze = lambda x: freeze(x, strict) + return pvector(map(curried_freeze, o)) if typ is tuple: - return tuple(map(freeze, o)) + curried_freeze = lambda x: freeze(x, strict) + return tuple(map(curried_freeze, o)) if typ is set: + # impossible to have anything that needs freezing inside a set or pset return pset(o) return o -def thaw(o): +def thaw(o, strict=True): """ Recursively convert pyrsistent containers into simple Python containers. @@ -48,6 +54,11 @@ def thaw(o): - pset is converted to set, but not recursively - tuple is converted to tuple, recursively. + If strict == True (the default): + + - thaw is called on elements of lists + - thaw is called on values in dicts + >>> from pyrsistent import s, m, v >>> thaw(s(1, 2)) {1, 2} @@ -56,14 +67,18 @@ def thaw(o): >>> thaw((1, v())) (1, []) """ - if isinstance(o, PVector): - return list(map(thaw, o)) - if isinstance(o, PMap): - return dict((k, thaw(v)) for k, v in o.iteritems()) + typ = type(o) + if isinstance(o, PVector) or (strict and typ is list): + curried_thaw = lambda x: thaw(x, strict) + return list(map(curried_thaw, o)) + if isinstance(o, PMap) or (strict and typ is dict): + return {k: thaw(v, strict) for k, v in o.items()} + if typ is tuple: + curried_thaw = lambda x: thaw(x, strict) + return tuple(map(curried_thaw, o)) if isinstance(o, PSet): + # impossible to thaw inside psets or sets return set(o) - if type(o) is tuple: - return tuple(map(thaw, o)) return o diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_immutable.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_immutable.py index a89bd7552..7c7594533 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_immutable.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_immutable.py @@ -1,7 +1,5 @@ import sys -import six - def immutable(members='', name='Immutable', verbose=False): """ @@ -48,7 +46,7 @@ def immutable(members='', name='Immutable', verbose=False): AttributeError: Cannot set frozen members id_ """ - if isinstance(members, six.string_types): + if isinstance(members, str): members = members.replace(',', ' ').split() def frozen_member_test(): @@ -98,8 +96,8 @@ def set(self, **kwargs): from collections import namedtuple namespace = dict(namedtuple=namedtuple, __name__='pyrsistent_immutable') try: - six.exec_(template, namespace) + exec(template, namespace) except SyntaxError as e: - raise SyntaxError(e.message + ':\n' + template) + raise SyntaxError(str(e) + ':\n' + template) from e - return namespace[name] \ No newline at end of file + return namespace[name] diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pbag.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pbag.py index 9905e9a6e..9cf5840b7 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pbag.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pbag.py @@ -1,4 +1,4 @@ -from ._compat import Container, Iterable, Sized, Hashable +from collections.abc import Container, Iterable, Sized, Hashable from functools import reduce from pyrsistent._pmap import pmap @@ -154,7 +154,7 @@ def __lt__(self, other): # Multiset-style operations similar to collections.Counter def __add__(self, other): - """ + """ Combine elements from two PBags. >>> pbag([1, 2, 2]) + pbag([2, 3, 3]) @@ -168,7 +168,7 @@ def __add__(self, other): return PBag(result.persistent()) def __sub__(self, other): - """ + """ Remove elements from one PBag that are present in another. >>> pbag([1, 2, 2, 2, 3]) - pbag([2, 3, 3, 4]) @@ -184,9 +184,9 @@ def __sub__(self, other): elif elem in self: result.remove(elem) return PBag(result.persistent()) - + def __or__(self, other): - """ + """ Union: Keep elements that are present in either of two PBags. >>> pbag([1, 2, 2, 2]) | pbag([2, 3, 3]) @@ -200,11 +200,11 @@ def __or__(self, other): newcount = max(count, other_count) result[elem] = newcount return PBag(result.persistent()) - + def __and__(self, other): """ Intersection: Only keep elements that are present in both PBags. - + >>> pbag([1, 2, 2, 2]) & pbag([2, 3, 3]) pbag([2]) """ @@ -216,7 +216,7 @@ def __and__(self, other): if newcount > 0: result[elem] = newcount return PBag(result.persistent()) - + def __hash__(self): """ Hash based on value of elements. diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pclass.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pclass.py index a437f7164..fd31a95d6 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pclass.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pclass.py @@ -1,4 +1,3 @@ -import six from pyrsistent._checked_types import (InvariantException, CheckedType, _restore_pickle, store_invariants) from pyrsistent._field_common import ( set_fields, check_type, is_field_ignore_extra_complaint, PFIELD_NO_INITIAL, serialize, check_global_invariants @@ -35,8 +34,7 @@ def _check_and_set_attr(cls, field, name, value, result, invariant_errors): setattr(result, name, value) -@six.add_metaclass(PClassMeta) -class PClass(CheckedType): +class PClass(CheckedType, metaclass=PClassMeta): """ A PClass is a python class with a fixed set of specified fields. PClasses are declared as python classes inheriting from PClass. It is defined the same way that PRecords are and behaves like a PRecord in all aspects except that it diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pdeque.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pdeque.py index 5147b3fa6..bd11bfa03 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pdeque.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pdeque.py @@ -1,4 +1,4 @@ -from ._compat import Sequence, Hashable +from collections.abc import Sequence, Hashable from itertools import islice, chain from numbers import Integral from pyrsistent._plist import plist @@ -276,8 +276,8 @@ def remove(self, elem): # This is severely inefficient with a double reverse, should perhaps implement a remove_last()? return PDeque(self._left_list, self._right_list.reverse().remove(elem).reverse(), self._length - 1) - except ValueError: - raise ValueError('{0} not found in PDeque'.format(elem)) + except ValueError as e: + raise ValueError('{0} not found in PDeque'.format(elem)) from e def reverse(self): """ diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_plist.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_plist.py index 8b4267f5e..bea7f5ecf 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_plist.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_plist.py @@ -1,4 +1,4 @@ -from ._compat import Sequence, Hashable +from collections.abc import Sequence, Hashable from numbers import Integral from functools import reduce @@ -143,7 +143,7 @@ def __lt__(self, other): def __eq__(self, other): """ Traverses the lists, checking equality of elements. - + This is an O(n) operation, but preserves the standard semantics of list equality. """ if not isinstance(other, _PListBase): @@ -179,8 +179,8 @@ def __getitem__(self, index): try: return self._drop(index).first - except AttributeError: - raise IndexError("PList index out of range") + except AttributeError as e: + raise IndexError("PList index out of range") from e def _drop(self, count): if count < 0: diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pmap.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pmap.py index e8a0ec53f..c6c7c7feb 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pmap.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pmap.py @@ -1,9 +1,107 @@ -from ._compat import Mapping, Hashable +from collections.abc import Mapping, Hashable from itertools import chain -import six from pyrsistent._pvector import pvector from pyrsistent._transformations import transform +class PMapView: + """View type for the persistent map/dict type `PMap`. + + Provides an equivalent of Python's built-in `dict_values` and `dict_items` + types that result from expreessions such as `{}.values()` and + `{}.items()`. The equivalent for `{}.keys()` is absent because the keys are + instead represented by a `PSet` object, which can be created in `O(1)` time. + + The `PMapView` class is overloaded by the `PMapValues` and `PMapItems` + classes which handle the specific case of values and items, respectively + + Parameters + ---------- + m : mapping + The mapping/dict-like object of which a view is to be created. This + should generally be a `PMap` object. + """ + # The public methods that use the above. + def __init__(self, m): + # Make sure this is a persistnt map + if not isinstance(m, PMap): + # We can convert mapping objects into pmap objects, I guess (but why?) + if isinstance(m, Mapping): + m = pmap(m) + else: + raise TypeError("PViewMap requires a Mapping object") + object.__setattr__(self, '_map', m) + + def __len__(self): + return len(self._map) + + def __setattr__(self, k, v): + raise TypeError("%s is immutable" % (type(self),)) + + def __reversed__(self): + raise TypeError("Persistent maps are not reversible") + +class PMapValues(PMapView): + """View type for the values of the persistent map/dict type `PMap`. + + Provides an equivalent of Python's built-in `dict_values` type that result + from expreessions such as `{}.values()`. See also `PMapView`. + + Parameters + ---------- + m : mapping + The mapping/dict-like object of which a view is to be created. This + should generally be a `PMap` object. + """ + def __iter__(self): + return self._map.itervalues() + + def __contains__(self, arg): + return arg in self._map.itervalues() + + # The str and repr methods imitate the dict_view style currently. + def __str__(self): + return f"pmap_values({list(iter(self))})" + + def __repr__(self): + return f"pmap_values({list(iter(self))})" + + def __eq__(self, x): + # For whatever reason, dict_values always seem to return False for == + # (probably it's not implemented), so we mimic that. + if x is self: return True + else: return False + +class PMapItems(PMapView): + """View type for the items of the persistent map/dict type `PMap`. + + Provides an equivalent of Python's built-in `dict_items` type that result + from expreessions such as `{}.items()`. See also `PMapView`. + + Parameters + ---------- + m : mapping + The mapping/dict-like object of which a view is to be created. This + should generally be a `PMap` object. + """ + def __iter__(self): + return self._map.iteritems() + + def __contains__(self, arg): + try: (k,v) = arg + except Exception: return False + return k in self._map and self._map[k] == v + + # The str and repr methods mitate the dict_view style currently. + def __str__(self): + return f"pmap_items({list(iter(self))})" + + def __repr__(self): + return f"pmap_items({list(iter(self))})" + + def __eq__(self, x): + if x is self: return True + elif not isinstance(x, type(self)): return False + else: return self._map == x._map class PMap(object): """ @@ -32,12 +130,12 @@ class PMap(object): >>> m1 = m(a=1, b=3) >>> m2 = m1.set('c', 3) >>> m3 = m2.remove('a') - >>> m1 - pmap({'b': 3, 'a': 1}) - >>> m2 - pmap({'c': 3, 'b': 3, 'a': 1}) - >>> m3 - pmap({'c': 3, 'b': 3}) + >>> m1 == {'a': 1, 'b': 3} + True + >>> m2 == {'a': 1, 'b': 3, 'c': 3} + True + >>> m3 == {'b': 3, 'c': 3} + True >>> m3['c'] 3 >>> m3.c @@ -90,13 +188,19 @@ def __contains__(self, key): def __iter__(self): return self.iterkeys() + # If this method is not defined, then reversed(pmap) will attempt to reverse + # the map using len() and getitem, usually resulting in a mysterious + # KeyError. + def __reversed__(self): + raise TypeError("Persistent maps are not reversible") + def __getattr__(self, key): try: return self[key] - except KeyError: + except KeyError as e: raise AttributeError( "{0} has no attribute '{1}'".format(type(self).__name__, key) - ) + ) from e def iterkeys(self): for k, _ in self.iteritems(): @@ -116,13 +220,14 @@ def iteritems(self): yield k, v def values(self): - return pvector(self.itervalues()) + return PMapValues(self) def keys(self): - return pvector(self.iterkeys()) + from ._pset import PSet + return PSet(self) def items(self): - return pvector(self.iteritems()) + return PMapItems(self) def __len__(self): return self._size @@ -146,7 +251,7 @@ def __eq__(self, other): return dict(self.iteritems()) == dict(other.iteritems()) elif isinstance(other, dict): return dict(self.iteritems()) == other - return dict(self.iteritems()) == dict(six.iteritems(other)) + return dict(self.iteritems()) == dict(other.items()) __ne__ = Mapping.__ne__ @@ -172,12 +277,12 @@ def set(self, key, val): >>> m1 = m(a=1, b=2) >>> m2 = m1.set('a', 3) >>> m3 = m1.set('c' ,4) - >>> m1 - pmap({'b': 2, 'a': 1}) - >>> m2 - pmap({'b': 2, 'a': 3}) - >>> m3 - pmap({'c': 4, 'b': 2, 'a': 1}) + >>> m1 == {'a': 1, 'b': 2} + True + >>> m2 == {'a': 3, 'b': 2} + True + >>> m3 == {'a': 1, 'b': 2, 'c': 4} + True """ return self.evolver().set(key, val).persistent() @@ -214,8 +319,8 @@ def update(self, *maps): maps the rightmost (last) value is inserted. >>> m1 = m(a=1, b=2) - >>> m1.update(m(a=2, c=3), {'a': 17, 'd': 35}) - pmap({'c': 3, 'b': 2, 'a': 17, 'd': 35}) + >>> m1.update(m(a=2, c=3), {'a': 17, 'd': 35}) == {'a': 17, 'b': 2, 'c': 3, 'd': 35} + True """ return self.update_with(lambda l, r: r, *maps) @@ -226,8 +331,8 @@ def update_with(self, update_fn, *maps): >>> from operator import add >>> m1 = m(a=1, b=2) - >>> m1.update_with(add, m(a=2)) - pmap({'b': 2, 'a': 3}) + >>> m1.update_with(add, m(a=2)) == {'a': 3, 'b': 2} + True The reverse behaviour of the regular merge. Keep the leftmost element instead of the rightmost. @@ -245,6 +350,8 @@ def update_with(self, update_fn, *maps): def __add__(self, other): return self.update(other) + __or__ = __add__ + def __reduce__(self): # Pickling support return pmap, (dict(self),) @@ -295,11 +402,9 @@ def __setitem__(self, key, val): self.set(key, val) def set(self, key, val): - if len(self._buckets_evolver) < 0.67 * self._size: - self._reallocate(2 * len(self._buckets_evolver)) - kv = (key, val) index, bucket = PMap._get_bucket(self._buckets_evolver, key) + reallocation_required = len(self._buckets_evolver) < 0.67 * self._size if bucket: for k, v in bucket: if k == key: @@ -309,17 +414,28 @@ def set(self, key, val): return self + # Only check and perform reallocation if not replacing an existing value. + # This is a performance tweak, see #247. + if reallocation_required: + self._reallocate() + return self.set(key, val) + new_bucket = [kv] new_bucket.extend(bucket) self._buckets_evolver[index] = new_bucket self._size += 1 else: + if reallocation_required: + self._reallocate() + return self.set(key, val) + self._buckets_evolver[index] = [kv] self._size += 1 return self - def _reallocate(self, new_size): + def _reallocate(self): + new_size = 2 * len(self._buckets_evolver) new_list = new_size * [None] buckets = self._buckets_evolver.persistent() for k, v in chain.from_iterable(x for x in buckets if x): @@ -380,15 +496,15 @@ def evolver(self): The underlying pmap remains the same: - >>> m1 - pmap({'b': 2, 'a': 1}) + >>> m1 == {'a': 1, 'b': 2} + True The changes are kept in the evolver. An updated pmap can be created using the persistent() function on the evolver. >>> m2 = e.persistent() - >>> m2 - pmap({'c': 3, 'b': 2}) + >>> m2 == {'b': 2, 'c': 3} + True The new pmap will share data with the original pmap in the same way that would have been done if only using operations on the pmap. @@ -418,7 +534,7 @@ def _turbo_mapping(initial, pre_size): # key collisions initial = dict(initial) - for k, v in six.iteritems(initial): + for k, v in initial.items(): h = hash(k) index = h % size bucket = buckets[index] @@ -441,10 +557,10 @@ def pmap(initial={}, pre_size=0): may have a positive performance impact in the cases where you know beforehand that a large number of elements will be inserted into the map eventually since it will reduce the number of reallocations required. - >>> pmap({'a': 13, 'b': 14}) - pmap({'b': 14, 'a': 13}) + >>> pmap({'a': 13, 'b': 14}) == {'a': 13, 'b': 14} + True """ - if not initial: + if not initial and pre_size == 0: return _EMPTY_PMAP return _turbo_mapping(initial, pre_size) @@ -452,9 +568,9 @@ def pmap(initial={}, pre_size=0): def m(**kwargs): """ - Creates a new persitent map. Inserts all key value arguments into the newly created map. + Creates a new persistent map. Inserts all key value arguments into the newly created map. - >>> m(a=13, b=14) - pmap({'b': 14, 'a': 13}) + >>> m(a=13, b=14) == {'a': 13, 'b': 14} + True """ return pmap(kwargs) diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_precord.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_precord.py index ec8d32c3d..1ee8198a1 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_precord.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_precord.py @@ -1,4 +1,3 @@ -import six from pyrsistent._checked_types import CheckedType, _restore_pickle, InvariantException, store_invariants from pyrsistent._field_common import ( set_fields, check_type, is_field_ignore_extra_complaint, PFIELD_NO_INITIAL, serialize, check_global_invariants @@ -23,8 +22,7 @@ def __new__(mcs, name, bases, dct): return super(_PRecordMeta, mcs).__new__(mcs, name, bases, dct) -@six.add_metaclass(_PRecordMeta) -class PRecord(PMap, CheckedType): +class PRecord(PMap, CheckedType, metaclass=_PRecordMeta): """ A PRecord is a PMap with a fixed set of specified fields. Records are declared as python classes inheriting from PRecord. Because it is a PMap it has full support for all Mapping methods such as iteration and element @@ -48,7 +46,7 @@ def __new__(cls, **kwargs): for k, v in cls._precord_initial_values.items()) initial_values.update(kwargs) - e = _PRecordEvolver(cls, pmap(), _factory_fields=factory_fields, _ignore_extra=ignore_extra) + e = _PRecordEvolver(cls, pmap(pre_size=len(cls._precord_fields)), _factory_fields=factory_fields, _ignore_extra=ignore_extra) for k, v in initial_values.items(): e[k] = v diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pset.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pset.py index a972ec533..4fae8278f 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pset.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pset.py @@ -1,9 +1,7 @@ -from ._compat import Set, Hashable +from collections.abc import Set, Hashable import sys from pyrsistent._pmap import pmap -PY2 = sys.version_info[0] < 3 - class PSet(object): """ @@ -44,7 +42,7 @@ def __len__(self): return len(self._map) def __repr__(self): - if PY2 or not self: + if not self: return 'p' + str(set(self)) return 'pset([{0}])'.format(str(set(self))[1:-1]) @@ -98,7 +96,7 @@ def remove(self, element): if element in self._map: return self.evolver().remove(element).persistent() - raise KeyError("Element '%s' not present in PSet" % element) + raise KeyError("Element '%s' not present in PSet" % repr(element)) def discard(self, element): """ diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pvector.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pvector.py index 82232782b..2aff0e8b4 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pvector.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_pvector.py @@ -1,8 +1,7 @@ from abc import abstractmethod, ABCMeta -from ._compat import Sequence, Hashable +from collections.abc import Sequence, Hashable from numbers import Integral import operator -import six from pyrsistent._transformations import transform @@ -411,8 +410,7 @@ def remove(self, value): l.remove(value) return _EMPTY_PVECTOR.extend(l) -@six.add_metaclass(ABCMeta) -class PVector(object): +class PVector(metaclass=ABCMeta): """ Persistent vector implementation. Meant as a replacement for the cases where you would normally use a Python list. diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_toolz.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_toolz.py index 6643ee860..0bf2cb144 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_toolz.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_toolz.py @@ -4,7 +4,7 @@ See https://github.com/pytoolz/toolz/. -toolz is relased under BSD licence. Below is the licence text +toolz is released under BSD licence. Below is the licence text from toolz as it appeared when copying the code. -------------------------------------------------------------- @@ -39,7 +39,7 @@ DAMAGE. """ import operator -from six.moves import reduce +from functools import reduce def get_in(keys, coll, default=None, no_default=False): @@ -72,7 +72,7 @@ def get_in(keys, coll, default=None, no_default=False): 0 >>> get_in(['y'], {}, no_default=True) Traceback (most recent call last): - ... + ... KeyError: 'y' """ try: @@ -80,4 +80,4 @@ def get_in(keys, coll, default=None, no_default=False): except (KeyError, IndexError, TypeError): if no_default: raise - return default \ No newline at end of file + return default diff --git a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_transformations.py b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_transformations.py index 612098969..7544843ac 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_transformations.py +++ b/conda_lock/_vendor/poetry/core/_vendor/pyrsistent/_transformations.py @@ -1,13 +1,9 @@ import re -import six try: from inspect import Parameter, signature except ImportError: signature = None - try: - from inspect import getfullargspec as getargspec - except ImportError: - from inspect import getargspec + from inspect import getfullargspec _EMPTY_SENTINEL = object() @@ -35,7 +31,7 @@ def discard(evolver, key): def rex(expr): """ Regular expression matcher to use together with transform functions """ r = re.compile(expr) - return lambda key: isinstance(key, six.string_types) and r.match(key) + return lambda key: isinstance(key, str) and r.match(key) def ny(_): @@ -107,7 +103,7 @@ def _get_keys_and_values(structure, key_spec): if signature is None: def _get_arity(f): - argspec = getargspec(f) + argspec = getfullargspec(f) return len(argspec.args) - len(argspec.defaults or ()) else: def _get_arity(f): diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/__init__.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/__init__.py index e0a7a542b..584bd96dc 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/__init__.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/__init__.py @@ -1,25 +1,55 @@ -from .api import aot -from .api import array -from .api import boolean -from .api import comment -from .api import date -from .api import datetime -from .api import document -from .api import dumps -from .api import float_ -from .api import inline_table -from .api import integer -from .api import item -from .api import key -from .api import key_value -from .api import loads -from .api import nl -from .api import parse -from .api import string -from .api import table -from .api import time -from .api import value -from .api import ws +from tomlkit.api import TOMLDocument +from tomlkit.api import aot +from tomlkit.api import array +from tomlkit.api import boolean +from tomlkit.api import comment +from tomlkit.api import date +from tomlkit.api import datetime +from tomlkit.api import document +from tomlkit.api import dump +from tomlkit.api import dumps +from tomlkit.api import float_ +from tomlkit.api import inline_table +from tomlkit.api import integer +from tomlkit.api import item +from tomlkit.api import key +from tomlkit.api import key_value +from tomlkit.api import load +from tomlkit.api import loads +from tomlkit.api import nl +from tomlkit.api import parse +from tomlkit.api import string +from tomlkit.api import table +from tomlkit.api import time +from tomlkit.api import value +from tomlkit.api import ws -__version__ = "0.7.0" +__version__ = "0.11.6" +__all__ = [ + "aot", + "array", + "boolean", + "comment", + "date", + "datetime", + "document", + "dump", + "dumps", + "float_", + "inline_table", + "integer", + "item", + "key", + "key_value", + "load", + "loads", + "nl", + "parse", + "string", + "table", + "time", + "TOMLDocument", + "value", + "ws", +] diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_compat.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_compat.py index 8d3b0ae3a..f1d3bccd6 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_compat.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_compat.py @@ -1,174 +1,22 @@ -import re +import contextlib import sys +from typing import Any +from typing import List +from typing import Optional -try: - from datetime import timezone -except ImportError: - from datetime import datetime - from datetime import timedelta - from datetime import tzinfo - class timezone(tzinfo): - __slots__ = "_offset", "_name" - - # Sentinel value to disallow None - _Omitted = object() - - def __new__(cls, offset, name=_Omitted): - if not isinstance(offset, timedelta): - raise TypeError("offset must be a timedelta") - if name is cls._Omitted: - if not offset: - return cls.utc - name = None - elif not isinstance(name, str): - raise TypeError("name must be a string") - if not cls._minoffset <= offset <= cls._maxoffset: - raise ValueError( - "offset must be a timedelta " - "strictly between -timedelta(hours=24) and " - "timedelta(hours=24)." - ) - return cls._create(offset, name) - - @classmethod - def _create(cls, offset, name=None): - self = tzinfo.__new__(cls) - self._offset = offset - self._name = name - return self - - def __getinitargs__(self): - """pickle support""" - if self._name is None: - return (self._offset,) - return (self._offset, self._name) - - def __eq__(self, other): - if type(other) != timezone: - return False - return self._offset == other._offset - - def __hash__(self): - return hash(self._offset) - - def __repr__(self): - """Convert to formal string, for repr(). - - >>> tz = timezone.utc - >>> repr(tz) - 'datetime.timezone.utc' - >>> tz = timezone(timedelta(hours=-5), 'EST') - >>> repr(tz) - "datetime.timezone(datetime.timedelta(-1, 68400), 'EST')" - """ - if self is self.utc: - return "datetime.timezone.utc" - if self._name is None: - return "%s.%s(%r)" % ( - self.__class__.__module__, - self.__class__.__name__, - self._offset, - ) - return "%s.%s(%r, %r)" % ( - self.__class__.__module__, - self.__class__.__name__, - self._offset, - self._name, - ) - - def __str__(self): - return self.tzname(None) - - def utcoffset(self, dt): - if isinstance(dt, datetime) or dt is None: - return self._offset - raise TypeError( - "utcoffset() argument must be a datetime instance" " or None" - ) - - def tzname(self, dt): - if isinstance(dt, datetime) or dt is None: - if self._name is None: - return self._name_from_offset(self._offset) - return self._name - raise TypeError("tzname() argument must be a datetime instance" " or None") - - def dst(self, dt): - if isinstance(dt, datetime) or dt is None: - return None - raise TypeError("dst() argument must be a datetime instance" " or None") - - def fromutc(self, dt): - if isinstance(dt, datetime): - if dt.tzinfo is not self: - raise ValueError("fromutc: dt.tzinfo " "is not self") - return dt + self._offset - raise TypeError("fromutc() argument must be a datetime instance" " or None") - - _maxoffset = timedelta(hours=23, minutes=59) - _minoffset = -_maxoffset - - @staticmethod - def _name_from_offset(delta): - if not delta: - return "UTC" - if delta < timedelta(0): - sign = "-" - delta = -delta - else: - sign = "+" - hours, rest = divmod(delta, timedelta(hours=1)) - minutes, rest = divmod(rest, timedelta(minutes=1)) - seconds = rest.seconds - microseconds = rest.microseconds - if microseconds: - return ("UTC{}{:02d}:{:02d}:{:02d}.{:06d}").format( - sign, hours, minutes, seconds, microseconds - ) - if seconds: - return "UTC{}{:02d}:{:02d}:{:02d}".format(sign, hours, minutes, seconds) - return "UTC{}{:02d}:{:02d}".format(sign, hours, minutes) - - timezone.utc = timezone._create(timedelta(0)) - timezone.min = timezone._create(timezone._minoffset) - timezone.max = timezone._create(timezone._maxoffset) - - -PY2 = sys.version_info[0] == 2 -PY36 = sys.version_info >= (3, 6) PY38 = sys.version_info >= (3, 8) -if PY2: - unicode = unicode - chr = unichr - long = long -else: - unicode = str - chr = chr - long = int - - -if PY36: - OrderedDict = dict -else: - from collections import OrderedDict - - -def decode(string, encodings=None): - if not PY2 and not isinstance(string, bytes): - return string - if PY2 and isinstance(string, unicode): +def decode(string: Any, encodings: Optional[List[str]] = None): + if not isinstance(string, bytes): return string encodings = encodings or ["utf-8", "latin1", "ascii"] for encoding in encodings: - try: + with contextlib.suppress(UnicodeEncodeError, UnicodeDecodeError): return string.decode(encoding) - except (UnicodeEncodeError, UnicodeDecodeError): - pass return string.decode(encodings[0], errors="ignore") diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_utils.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_utils.py index 2ae3e4246..85958e93c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_utils.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/_utils.py @@ -1,28 +1,24 @@ import re +from collections.abc import Mapping from datetime import date from datetime import datetime from datetime import time from datetime import timedelta +from datetime import timezone +from typing import Collection from typing import Union -from ._compat import decode -from ._compat import timezone - - -try: - from collections.abc import Mapping -except ImportError: - from collections import Mapping +from tomlkit._compat import decode RFC_3339_LOOSE = re.compile( "^" r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date "(" - "([T ])?" # Separator + "([Tt ])?" # Separator r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time - r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone + r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone ")?" "$" ) @@ -30,9 +26,9 @@ RFC_3339_DATETIME = re.compile( "^" "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date - "[T ]" # Separator + "[Tt ]" # Separator r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time - r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone + r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone "$" ) @@ -45,7 +41,7 @@ _utc = timezone(timedelta(), "UTC") -def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] +def parse_rfc3339(string: str) -> Union[datetime, date, time]: m = RFC_3339_DATETIME.match(string) if m: year = int(m.group(1)) @@ -57,12 +53,12 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] microsecond = 0 if m.group(7): - microsecond = int(("{:<06s}".format(m.group(8)))[:6]) + microsecond = int((f"{m.group(8):<06s}")[:6]) if m.group(9): # Timezone tz = m.group(9) - if tz == "Z": + if tz.upper() == "Z": tzinfo = _utc else: sign = m.group(11)[0] @@ -71,9 +67,7 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] if sign == "-": offset = -offset - tzinfo = timezone( - offset, "{}{}:{}".format(sign, m.group(12), m.group(13)) - ) + tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}") return datetime( year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo @@ -97,38 +91,55 @@ def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time] microsecond = 0 if m.group(4): - microsecond = int(("{:<06s}".format(m.group(5)))[:6]) + microsecond = int((f"{m.group(5):<06s}")[:6]) return time(hour, minute, second, microsecond) raise ValueError("Invalid RFC 339 string") -_escaped = {"b": "\b", "t": "\t", "n": "\n", "f": "\f", "r": "\r", '"': '"', "\\": "\\"} -_escapes = {v: k for k, v in _escaped.items()} +# https://toml.io/en/v1.0.0#string +CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)} +_escaped = { + "b": "\b", + "t": "\t", + "n": "\n", + "f": "\f", + "r": "\r", + '"': '"', + "\\": "\\", +} +_compact_escapes = { + **{v: f"\\{k}" for k, v in _escaped.items()}, + '"""': '""\\"', +} +_basic_escapes = CONTROL_CHARS | {'"', "\\"} + + +def _unicode_escape(seq: str) -> str: + return "".join(f"\\u{ord(c):04x}" for c in seq) -def escape_string(s): +def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str: s = decode(s) res = [] start = 0 - def flush(): + def flush(inc=1): if start != i: res.append(s[start:i]) - return i + 1 + return i + inc i = 0 while i < len(s): - c = s[i] - if c in '"\\\n\r\t\b\f': - start = flush() - res.append("\\" + _escapes[c]) - elif ord(c) < 0x20: - start = flush() - res.append("\\u%04x" % ord(c)) + for seq in escape_sequences: + seq_len = len(seq) + if s[i:].startswith(seq): + start = flush(seq_len) + res.append(_compact_escapes.get(seq) or _unicode_escape(seq)) + i += seq_len - 1 # fast-forward escape sequence i += 1 flush() @@ -136,9 +147,9 @@ def flush(): return "".join(res) -def merge_dicts(d1, d2): +def merge_dicts(d1: dict, d2: dict) -> dict: for k, v in d2.items(): - if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): - merge_dicts(d1[k], d2[k]) + if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): + merge_dicts(d1[k], v) else: d1[k] = d2[k] diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/api.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/api.py index 3de412196..ed48ca9a7 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/api.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/api.py @@ -1,31 +1,40 @@ import datetime as _datetime +from collections.abc import Mapping +from typing import IO +from typing import Iterable +from typing import Optional from typing import Tuple - -from ._utils import parse_rfc3339 -from .container import Container -from .items import AoT -from .items import Array -from .items import Bool -from .items import Comment -from .items import Date -from .items import DateTime -from .items import Float -from .items import InlineTable -from .items import Integer -from .items import Item as _Item -from .items import Key -from .items import String -from .items import Table -from .items import Time -from .items import Trivia -from .items import Whitespace -from .items import item -from .parser import Parser -from .toml_document import TOMLDocument as _TOMLDocument - - -def loads(string): # type: (str) -> _TOMLDocument +from typing import Union + +from tomlkit._utils import parse_rfc3339 +from tomlkit.container import Container +from tomlkit.exceptions import UnexpectedCharError +from tomlkit.items import AoT +from tomlkit.items import Array +from tomlkit.items import Bool +from tomlkit.items import Comment +from tomlkit.items import Date +from tomlkit.items import DateTime +from tomlkit.items import DottedKey +from tomlkit.items import Float +from tomlkit.items import InlineTable +from tomlkit.items import Integer +from tomlkit.items import Item as _Item +from tomlkit.items import Key +from tomlkit.items import SingleKey +from tomlkit.items import String +from tomlkit.items import StringType as _StringType +from tomlkit.items import Table +from tomlkit.items import Time +from tomlkit.items import Trivia +from tomlkit.items import Whitespace +from tomlkit.items import item +from tomlkit.parser import Parser +from tomlkit.toml_document import TOMLDocument + + +def loads(string: Union[str, bytes]) -> TOMLDocument: """ Parses a string into a TOMLDocument. @@ -34,48 +43,95 @@ def loads(string): # type: (str) -> _TOMLDocument return parse(string) -def dumps(data, sort_keys=False): # type: (_TOMLDocument, bool) -> str +def dumps(data: Mapping, sort_keys: bool = False) -> str: """ Dumps a TOMLDocument into a string. """ - if not isinstance(data, _TOMLDocument) and isinstance(data, dict): - data = item(data, _sort_keys=sort_keys) + if not isinstance(data, Container) and isinstance(data, Mapping): + data = item(dict(data), _sort_keys=sort_keys) - return data.as_string() + try: + # data should be a `Container` (and therefore implement `as_string`) + # for all type safe invocations of this function + return data.as_string() # type: ignore[attr-defined] + except AttributeError as ex: + msg = f"Expecting Mapping or TOML Container, {type(data)} given" + raise TypeError(msg) from ex -def parse(string): # type: (str) -> _TOMLDocument +def load(fp: IO) -> TOMLDocument: """ - Parses a string into a TOMLDocument. + Load toml document from a file-like object. + """ + return parse(fp.read()) + + +def dump(data: Mapping, fp: IO[str], *, sort_keys: bool = False) -> None: + """ + Dump a TOMLDocument into a writable file stream. + + :param data: a dict-like object to dump + :param sort_keys: if true, sort the keys in alphabetic order + """ + fp.write(dumps(data, sort_keys=sort_keys)) + + +def parse(string: Union[str, bytes]) -> TOMLDocument: + """ + Parses a string or bytes into a TOMLDocument. """ return Parser(string).parse() -def document(): # type: () -> _TOMLDocument +def document() -> TOMLDocument: """ Returns a new TOMLDocument instance. """ - return _TOMLDocument() + return TOMLDocument() # Items -def integer(raw): # type: (str) -> Integer +def integer(raw: Union[str, int]) -> Integer: + """Create an integer item from a number or string.""" return item(int(raw)) -def float_(raw): # type: (str) -> Float +def float_(raw: Union[str, float]) -> Float: + """Create an float item from a number or string.""" return item(float(raw)) -def boolean(raw): # type: (str) -> Bool +def boolean(raw: str) -> Bool: + """Turn `true` or `false` into a boolean item.""" return item(raw == "true") -def string(raw): # type: (str) -> String - return item(raw) +def string( + raw: str, + *, + literal: bool = False, + multiline: bool = False, + escape: bool = True, +) -> String: + """Create a string item. + + By default, this function will create *single line basic* strings, but + boolean flags (e.g. ``literal=True`` and/or ``multiline=True``) + can be used for personalization. + + For more information, please check the spec: `https://toml.io/en/v1.0.0#string`_. + + Common escaping rules will be applied for basic strings. + This can be controlled by explicitly setting ``escape=False``. + Please note that, if you disable escaping, you will have to make sure that + the given strings don't contain any forbidden character or sequence. + """ + type_ = _StringType.select(literal, multiline) + return String.from_raw(raw, type_, escape) -def date(raw): # type: (str) -> Date +def date(raw: str) -> Date: + """Create a TOML date.""" value = parse_rfc3339(raw) if not isinstance(value, _datetime.date): raise ValueError("date() only accepts date strings.") @@ -83,7 +139,8 @@ def date(raw): # type: (str) -> Date return item(value) -def time(raw): # type: (str) -> Time +def time(raw: str) -> Time: + """Create a TOML time.""" value = parse_rfc3339(raw) if not isinstance(value, _datetime.time): raise ValueError("time() only accepts time strings.") @@ -91,7 +148,8 @@ def time(raw): # type: (str) -> Time return item(value) -def datetime(raw): # type: (str) -> DateTime +def datetime(raw: str) -> DateTime: + """Create a TOML datetime.""" value = parse_rfc3339(raw) if not isinstance(value, _datetime.datetime): raise ValueError("datetime() only accepts datetime strings.") @@ -99,44 +157,131 @@ def datetime(raw): # type: (str) -> DateTime return item(value) -def array(raw=None): # type: (str) -> Array +def array(raw: str = None) -> Array: + """Create an array item for its string representation. + + :Example: + + >>> array("[1, 2, 3]") # Create from a string + [1, 2, 3] + >>> a = array() + >>> a.extend([1, 2, 3]) # Create from a list + >>> a + [1, 2, 3] + """ if raw is None: raw = "[]" return value(raw) -def table(): # type: () -> Table - return Table(Container(), Trivia(), False) +def table(is_super_table: Optional[bool] = None) -> Table: + """Create an empty table. + + :param is_super_table: if true, the table is a super table + :Example: -def inline_table(): # type: () -> InlineTable + >>> doc = document() + >>> foo = table(True) + >>> bar = table() + >>> bar.update({'x': 1}) + >>> foo.append('bar', bar) + >>> doc.append('foo', foo) + >>> print(doc.as_string()) + [foo.bar] + x = 1 + """ + return Table(Container(), Trivia(), False, is_super_table) + + +def inline_table() -> InlineTable: + """Create an inline table. + + :Example: + + >>> table = inline_table() + >>> table.update({'x': 1, 'y': 2}) + >>> print(table.as_string()) + {x = 1, y = 2} + """ return InlineTable(Container(), Trivia(), new=True) -def aot(): # type: () -> AoT +def aot() -> AoT: + """Create an array of table. + + :Example: + + >>> doc = document() + >>> aot = aot() + >>> aot.append(item({'x': 1})) + >>> doc.append('foo', aot) + >>> print(doc.as_string()) + [[foo]] + x = 1 + """ return AoT([]) -def key(k): # type: (str) -> Key - return Key(k) +def key(k: Union[str, Iterable[str]]) -> Key: + """Create a key from a string. When a list of string is given, + it will create a dotted key. + :Example: -def value(raw): # type: (str) -> _Item - return Parser(raw)._parse_value() + >>> doc = document() + >>> doc.append(key('foo'), 1) + >>> doc.append(key(['bar', 'baz']), 2) + >>> print(doc.as_string()) + foo = 1 + bar.baz = 2 + """ + if isinstance(k, str): + return SingleKey(k) + return DottedKey([key(_k) for _k in k]) + + +def value(raw: str) -> _Item: + """Parse a simple value from a string. + :Example: + + >>> value("1") + 1 + >>> value("true") + True + >>> value("[1, 2, 3]") + [1, 2, 3] + """ + parser = Parser(raw) + v = parser._parse_value() + if not parser.end(): + raise parser.parse_error(UnexpectedCharError, char=parser._current) + return v -def key_value(src): # type: (str) -> Tuple[Key, _Item] + +def key_value(src: str) -> Tuple[Key, _Item]: + """Parse a key-value pair from a string. + + :Example: + + >>> key_value("foo = 1") + (Key('foo'), 1) + """ return Parser(src)._parse_key_value() -def ws(src): # type: (str) -> Whitespace +def ws(src: str) -> Whitespace: + """Create a whitespace from a string.""" return Whitespace(src, fixed=True) -def nl(): # type: () -> Whitespace +def nl() -> Whitespace: + """Create a newline item.""" return ws("\n") -def comment(string): # type: (str) -> Comment +def comment(string: str) -> Comment: + """Create a comment item.""" return Comment(Trivia(comment_ws=" ", comment="# " + string)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/container.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/container.py index 6386e738c..4b40a13b7 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/container.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/container.py @@ -1,51 +1,72 @@ -from __future__ import unicode_literals - import copy from typing import Any from typing import Dict -from typing import Generator +from typing import Iterator from typing import List from typing import Optional from typing import Tuple from typing import Union -from ._compat import decode -from ._utils import merge_dicts -from .exceptions import KeyAlreadyPresent -from .exceptions import NonExistentKey -from .exceptions import ParseError -from .exceptions import TOMLKitError -from .items import AoT -from .items import Comment -from .items import Item -from .items import Key -from .items import Null -from .items import Table -from .items import Whitespace -from .items import item as _item +from tomlkit._compat import decode +from tomlkit._utils import merge_dicts +from tomlkit.exceptions import KeyAlreadyPresent +from tomlkit.exceptions import NonExistentKey +from tomlkit.exceptions import TOMLKitError +from tomlkit.items import AoT +from tomlkit.items import Comment +from tomlkit.items import Item +from tomlkit.items import Key +from tomlkit.items import Null +from tomlkit.items import SingleKey +from tomlkit.items import Table +from tomlkit.items import Trivia +from tomlkit.items import Whitespace +from tomlkit.items import _CustomDict +from tomlkit.items import item as _item _NOT_SET = object() -class Container(dict): +class Container(_CustomDict): """ A container for items within a TOMLDocument. + + This class implements the `dict` interface with copy/deepcopy protocol. """ - def __init__(self, parsed=False): # type: (bool) -> None - self._map = {} # type: Dict[Key, int] - self._body = [] # type: List[Tuple[Optional[Key], Item]] + def __init__(self, parsed: bool = False) -> None: + self._map: Dict[Key, int] = {} + self._body: List[Tuple[Optional[Key], Item]] = [] self._parsed = parsed self._table_keys = [] @property - def body(self): # type: () -> List[Tuple[Optional[Key], Item]] + def body(self) -> List[Tuple[Optional[Key], Item]]: return self._body + def unwrap(self) -> Dict[str, Any]: + unwrapped = {} + for k, v in self.items(): + if k is None: + continue + + if isinstance(k, Key): + k = k.key + + if isinstance(v, Item): + v = v.unwrap() + + if k in unwrapped: + merge_dicts(unwrapped[k], v) + else: + unwrapped[k] = v + + return unwrapped + @property - def value(self): # type: () -> Dict[Any, Any] + def value(self) -> Dict[str, Any]: d = {} for k, v in self._body: if k is None: @@ -64,10 +85,10 @@ def value(self): # type: () -> Dict[Any, Any] return d - def parsing(self, parsing): # type: (bool) -> None + def parsing(self, parsing: bool) -> None: self._parsed = parsing - for k, v in self._body: + for _, v in self._body: if isinstance(v, Table): v.value.parsing(parsing) elif isinstance(v, AoT): @@ -75,10 +96,17 @@ def parsing(self, parsing): # type: (bool) -> None t.value.parsing(parsing) def add( - self, key, item=None - ): # type: (Union[Key, Item, str], Optional[Item]) -> Container + self, key: Union[Key, Item, str], item: Optional[Item] = None + ) -> "Container": """ Adds an item to the current Container. + + :Example: + + >>> # add a key-value pair + >>> doc.add('key', 'value') + >>> # add a comment or whitespace or newline + >>> doc.add(comment('# comment')) """ if item is None: if not isinstance(key, (Comment, Whitespace)): @@ -90,29 +118,91 @@ def add( return self.append(key, item) - def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container + def _handle_dotted_key(self, key: Key, value: Item) -> None: + names = tuple(iter(key)) + name = names[0] + name._dotted = True + if name in self: + if not isinstance(value, Table): + table = Table(Container(True), Trivia(), False, is_super_table=True) + _table = table + for i, _name in enumerate(names[1:]): + if i == len(names) - 2: + _name.sep = key.sep + + _table.append(_name, value) + else: + _name._dotted = True + _table.append( + _name, + Table( + Container(True), + Trivia(), + False, + is_super_table=i < len(names) - 2, + ), + ) + + _table = _table[_name] + + value = table + + self.append(name, value) + + return + else: + table = Table(Container(True), Trivia(), False, is_super_table=True) + self.append(name, table) + + for i, _name in enumerate(names[1:]): + if i == len(names) - 2: + _name.sep = key.sep + + table.append(_name, value) + else: + _name._dotted = True + if _name in table.value: + table = table.value[_name] + else: + table.append( + _name, + Table( + Container(True), + Trivia(), + False, + is_super_table=i < len(names) - 2, + ), + ) + + table = table[_name] + + def append(self, key: Union[Key, str, None], item: Item) -> "Container": + """Similar to :meth:`add` but both key and value must be given.""" if not isinstance(key, Key) and key is not None: - key = Key(key) + key = SingleKey(key) if not isinstance(item, Item): item = _item(item) + if key is not None and key.is_multi(): + self._handle_dotted_key(key, item) + return self + if isinstance(item, (AoT, Table)) and item.name is None: item.name = key.key - if ( - isinstance(item, Table) - and self._body - and not self._parsed - and not item.trivia.indent - ): - item.trivia.indent = "\n" + prev = self._previous_item() + prev_ws = isinstance(prev, Whitespace) or ends_with_whitespace(prev) + if isinstance(item, Table): + if not self._parsed: + item.invalidate_display_name() + if self._body and not (self._parsed or item.trivia.indent or prev_ws): + item.trivia.indent = "\n" if isinstance(item, AoT) and self._body and not self._parsed: - if item and "\n" not in item[0].trivia.indent: + item.invalidate_display_name() + if item and not ("\n" in item[0].trivia.indent or prev_ws): item[0].trivia.indent = "\n" + item[0].trivia.indent - else: - self.append(None, Whitespace("\n")) if key is not None and key in self: current_idx = self._map[key] @@ -166,8 +256,15 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container return self + # Create a new element to replace the old one + current = copy.deepcopy(current) for k, v in item.value.body: current.append(k, v) + self._body[ + current_idx[-1] + if isinstance(current_idx, tuple) + else current_idx + ] = (current_body_element[0], current) return self elif current_body_element[0].is_dotted(): @@ -193,11 +290,9 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container # item that is not a table and insert after it # If no such item exists, insert at the top of the table key_after = None - idx = 0 - for k, v in self._body: + for i, (k, v) in enumerate(self._body): if isinstance(v, Null): - # This happens only after deletion - continue + continue # Null elements are inserted after deletion if isinstance(v, Whitespace) and not v.is_fixed(): continue @@ -205,19 +300,19 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container if not is_table and isinstance(v, (Table, AoT)): break - key_after = k or idx - idx += 1 + key_after = k or i # last scalar, Array or InlineTable value if key_after is not None: if isinstance(key_after, int): - if key_after + 1 < len(self._body) - 1: + if key_after + 1 < len(self._body): return self._insert_at(key_after + 1, key, item) else: previous_item = self._body[-1][1] - if ( - not isinstance(previous_item, Whitespace) - and not is_table - and "\n" not in previous_item.trivia.trail + if not ( + isinstance(previous_item, Whitespace) + or ends_with_whitespace(previous_item) + or is_table + or "\n" in previous_item.trivia.trail ): previous_item.trivia.trail += "\n" else: @@ -247,13 +342,33 @@ def append(self, key, item): # type: (Union[Key, str, None], Item) -> Container self._table_keys.append(key) if key is not None: - super(Container, self).__setitem__(key.key, item.value) + dict.__setitem__(self, key.key, item.value) return self - def remove(self, key): # type: (Union[Key, str]) -> Container + def _remove_at(self, idx: int) -> None: + key = self._body[idx][0] + index = self._map.get(key) + if index is None: + raise NonExistentKey(key) + self._body[idx] = (None, Null()) + + if isinstance(index, tuple): + index = list(index) + index.remove(idx) + if len(index) == 1: + index = index.pop() + else: + index = tuple(index) + self._map[key] = index + else: + dict.__delitem__(self, key.key) + self._map.pop(key) + + def remove(self, key: Union[Key, str]) -> "Container": + """Remove a key from the container.""" if not isinstance(key, Key): - key = Key(key) + key = SingleKey(key) idx = self._map.pop(key, None) if idx is None: @@ -265,13 +380,13 @@ def remove(self, key): # type: (Union[Key, str]) -> Container else: self._body[idx] = (None, Null()) - super(Container, self).__delitem__(key.key) + dict.__delitem__(self, key.key) return self def _insert_after( - self, key, other_key, item - ): # type: (Union[str, Key], Union[str, Key], Union[Item, Any]) -> Container + self, key: Union[Key, str], other_key: Union[Key, str], item: Any + ) -> "Container": if key is None: raise ValueError("Key cannot be null in insert_after()") @@ -279,10 +394,10 @@ def _insert_after( raise NonExistentKey(key) if not isinstance(key, Key): - key = Key(key) + key = SingleKey(key) if not isinstance(other_key, Key): - other_key = Key(other_key) + other_key = SingleKey(other_key) item = _item(item) @@ -312,27 +427,26 @@ def _insert_after( self._body.insert(idx + 1, (other_key, item)) if key is not None: - super(Container, self).__setitem__(other_key.key, item.value) + dict.__setitem__(self, other_key.key, item.value) return self - def _insert_at( - self, idx, key, item - ): # type: (int, Union[str, Key], Union[Item, Any]) -> Container + def _insert_at(self, idx: int, key: Union[Key, str], item: Any) -> "Container": if idx > len(self._body) - 1: - raise ValueError("Unable to insert at position {}".format(idx)) + raise ValueError(f"Unable to insert at position {idx}") if not isinstance(key, Key): - key = Key(key) + key = SingleKey(key) item = _item(item) if idx > 0: previous_item = self._body[idx - 1][1] - if ( - not isinstance(previous_item, Whitespace) - and not isinstance(item, (AoT, Table)) - and "\n" not in previous_item.trivia.trail + if not ( + isinstance(previous_item, Whitespace) + or ends_with_whitespace(previous_item) + or isinstance(item, (AoT, Table)) + or "\n" in previous_item.trivia.trail ): previous_item.trivia.trail += "\n" @@ -354,13 +468,14 @@ def _insert_at( self._body.insert(idx, (key, item)) if key is not None: - super(Container, self).__setitem__(key.key, item.value) + dict.__setitem__(self, key.key, item.value) return self - def item(self, key): # type: (Union[Key, str]) -> Item + def item(self, key: Union[Key, str]) -> Item: + """Get an item for the given key.""" if not isinstance(key, Key): - key = Key(key) + key = SingleKey(key) idx = self._map.get(key, None) if idx is None: @@ -374,11 +489,13 @@ def item(self, key): # type: (Union[Key, str]) -> Item return self._body[idx][1] - def last_item(self): # type: () -> Optional[Item] + def last_item(self) -> Optional[Item]: + """Get the last item.""" if self._body: return self._body[-1][1] - def as_string(self): # type: () -> str + def as_string(self) -> str: + """Render as TOML string.""" s = "" for k, v in self._body: if k is not None: @@ -394,8 +511,8 @@ def as_string(self): # type: () -> str return s def _render_table( - self, key, table, prefix=None - ): # (Key, Table, Optional[str]) -> str + self, key: Key, table: Table, prefix: Optional[str] = None + ) -> str: cur = "" if table.display_name is not None: @@ -408,7 +525,8 @@ def _render_table( if not table.is_super_table() or ( any( - not isinstance(v, (Table, AoT, Whitespace)) for _, v in table.value.body + not isinstance(v, (Table, AoT, Whitespace, Null)) + for _, v in table.value.body ) and not key.is_dotted() ): @@ -416,16 +534,21 @@ def _render_table( if table.is_aot_element(): open_, close = "[[", "]]" - cur += "{}{}{}{}{}{}{}{}".format( - table.trivia.indent, - open_, - decode(_key), - close, - table.trivia.comment_ws, - decode(table.trivia.comment), - table.trivia.trail, - "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "", + newline_in_table_trivia = ( + "\n" if "\n" not in table.trivia.trail and len(table.value) > 0 else "" + ) + cur += ( + f"{table.trivia.indent}" + f"{open_}" + f"{decode(_key)}" + f"{close}" + f"{table.trivia.comment_ws}" + f"{decode(table.trivia.comment)}" + f"{table.trivia.trail}" + f"{newline_in_table_trivia}" ) + elif table.trivia.indent == "\n": + cur += table.trivia.indent for k, v in table.value.body: if isinstance(v, Table): @@ -458,7 +581,7 @@ def _render_aot(self, key, aot, prefix=None): return cur - def _render_aot_table(self, table, prefix=None): # (Table, Optional[str]) -> str + def _render_aot_table(self, table: Table, prefix: Optional[str] = None) -> str: cur = "" _key = prefix or "" @@ -466,14 +589,14 @@ def _render_aot_table(self, table, prefix=None): # (Table, Optional[str]) -> st if not table.is_super_table(): open_, close = "[[", "]]" - cur += "{}{}{}{}{}{}{}".format( - table.trivia.indent, - open_, - decode(_key), - close, - table.trivia.comment_ws, - decode(table.trivia.comment), - table.trivia.trail, + cur += ( + f"{table.trivia.indent}" + f"{open_}" + f"{decode(_key)}" + f"{close}" + f"{table.trivia.comment_ws}" + f"{decode(table.trivia.comment)}" + f"{table.trivia.trail}" ) for k, v in table.value.body: @@ -501,75 +624,26 @@ def _render_simple_item(self, key, item, prefix=None): if prefix is not None: _key = prefix + "." + _key - return "{}{}{}{}{}{}{}".format( - item.trivia.indent, - decode(_key), - key.sep, - decode(item.as_string()), - item.trivia.comment_ws, - decode(item.trivia.comment), - item.trivia.trail, + return ( + f"{item.trivia.indent}" + f"{decode(_key)}" + f"{key.sep}" + f"{decode(item.as_string())}" + f"{item.trivia.comment_ws}" + f"{decode(item.trivia.comment)}" + f"{item.trivia.trail}" ) - # Dictionary methods - - def keys(self): # type: () -> Generator[str] - return super(Container, self).keys() - - def values(self): # type: () -> Generator[Item] - for k in self.keys(): - yield self[k] - - def items(self): # type: () -> Generator[Item] - for k, v in self.value.items(): - if k is None: - continue - - yield k, v - - def update(self, other): # type: (Dict) -> None - for k, v in other.items(): - self[k] = v - - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - if not isinstance(key, Key): - key = Key(key) - - if key not in self: - return default - - return self[key] - - def pop(self, key, default=_NOT_SET): - try: - value = self[key] - except KeyError: - if default is _NOT_SET: - raise - - return default - - del self[key] - - return value + def __len__(self) -> int: + return dict.__len__(self) - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - if key not in self: - self[key] = default - - return self[key] + def __iter__(self) -> Iterator[str]: + return iter(dict.keys(self)) - def __contains__(self, key): # type: (Union[Key, str]) -> bool - if not isinstance(key, Key): - key = Key(key) - - return key in self._map - - def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container] + # Dictionary methods + def __getitem__(self, key: Union[Key, str]) -> Union[Item, "Container"]: if not isinstance(key, Key): - key = Key(key) + key = SingleKey(key) idx = self._map.get(key, None) if idx is None: @@ -587,23 +661,25 @@ def __getitem__(self, key): # type: (Union[Key, str]) -> Union[Item, Container] return item - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None + def __setitem__(self, key: Union[Key, str], value: Any) -> None: if key is not None and key in self: - self._replace(key, key, value) + old_key = next(filter(lambda k: k == key, self._map)) + self._replace(old_key, key, value) else: self.append(key, value) - def __delitem__(self, key): # type: (Union[Key, str]) -> None + def __delitem__(self, key: Union[Key, str]) -> None: self.remove(key) + def setdefault(self, key: Union[Key, str], default: Any) -> Any: + super().setdefault(key, default=default) + return self[key] + def _replace( - self, key, new_key, value - ): # type: (Union[Key, str], Union[Key, str], Item) -> None + self, key: Union[Key, str], new_key: Union[Key, str], value: Item + ) -> None: if not isinstance(key, Key): - key = Key(key) - - if not isinstance(new_key, Key): - new_key = Key(new_key) + key = SingleKey(key) idx = self._map.get(key, None) if idx is None: @@ -612,10 +688,9 @@ def _replace( self._replace_at(idx, new_key, value) def _replace_at( - self, idx, new_key, value - ): # type: (Union[int, Tuple[int]], Union[Key, str], Item) -> None - if not isinstance(new_key, Key): - new_key = Key(new_key) + self, idx: Union[int, Tuple[int]], new_key: Union[Key, str], value: Item + ) -> None: + value = _item(value) if isinstance(idx, tuple): for i in idx[1:]: @@ -624,38 +699,63 @@ def _replace_at( idx = idx[0] k, v = self._body[idx] + if not isinstance(new_key, Key): + if ( + isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)) + or new_key != k.key + ): + new_key = SingleKey(new_key) + else: # Inherit the sep of the old key + new_key = k - self._map[new_key] = self._map.pop(k) + del self._map[k] + self._map[new_key] = idx if new_key != k: - super(Container, self).__delitem__(k) - - if isinstance(self._map[new_key], tuple): - self._map[new_key] = self._map[new_key][0] - - value = _item(value) + dict.__delitem__(self, k) + + if isinstance(value, (AoT, Table)) != isinstance(v, (AoT, Table)): + # new tables should appear after all non-table values + self.remove(k) + for i in range(idx, len(self._body)): + if isinstance(self._body[i][1], (AoT, Table)): + self._insert_at(i, new_key, value) + idx = i + break + else: + idx = -1 + self.append(new_key, value) + else: + # Copying trivia + if not isinstance(value, (Whitespace, AoT)): + value.trivia.indent = v.trivia.indent + value.trivia.comment_ws = value.trivia.comment_ws or v.trivia.comment_ws + value.trivia.comment = value.trivia.comment or v.trivia.comment + value.trivia.trail = v.trivia.trail + self._body[idx] = (new_key, value) - # Copying trivia - if not isinstance(value, (Whitespace, AoT)): - value.trivia.indent = v.trivia.indent - value.trivia.comment_ws = v.trivia.comment_ws - value.trivia.comment = v.trivia.comment - value.trivia.trail = v.trivia.trail + if hasattr(value, "invalidate_display_name"): + value.invalidate_display_name() # type: ignore[attr-defined] if isinstance(value, Table): - # Insert a cosmetic new line for tables - value.append(None, Whitespace("\n")) - - self._body[idx] = (new_key, value) - - super(Container, self).__setitem__(new_key.key, value.value) - - def __str__(self): # type: () -> str + # Insert a cosmetic new line for tables if: + # - it does not have it yet OR is not followed by one + # - it is not the last item + last, _ = self._previous_item_with_index() + idx = last if idx < 0 else idx + has_ws = ends_with_whitespace(value) + next_ws = idx < last and isinstance(self._body[idx + 1][1], Whitespace) + if idx < last and not (next_ws or has_ws): + value.append(None, Whitespace("\n")) + + dict.__setitem__(self, new_key.key, value.value) + + def __str__(self) -> str: return str(self.value) - def __repr__(self): # type: () -> str - return super(Container, self).__repr__() + def __repr__(self) -> str: + return repr(self.value) - def __eq__(self, other): # type: (Dict) -> bool + def __eq__(self, other: dict) -> bool: if not isinstance(other, dict): return NotImplemented @@ -671,38 +771,63 @@ def __reduce_ex__(self, protocol): return ( self.__class__, self._getstate(protocol), - (self._map, self._body, self._parsed), + (self._map, self._body, self._parsed, self._table_keys), ) def __setstate__(self, state): self._map = state[0] self._body = state[1] self._parsed = state[2] + self._table_keys = state[3] - def copy(self): # type: () -> Container + for key, item in self._body: + if key is not None: + dict.__setitem__(self, key.key, item.value) + + def copy(self) -> "Container": return copy.copy(self) - def __copy__(self): # type: () -> Container + def __copy__(self) -> "Container": c = self.__class__(self._parsed) - for k, v in super(Container, self).copy().items(): - super(Container, c).__setitem__(k, v) + for k, v in dict.items(self): + dict.__setitem__(c, k, v) c._body += self.body c._map.update(self._map) return c + def _previous_item_with_index( + self, idx: Optional[int] = None, ignore=(Null,) + ) -> Optional[Tuple[int, Item]]: + """Find the immediate previous item before index ``idx``""" + if idx is None or idx > len(self._body): + idx = len(self._body) + for i in range(idx - 1, -1, -1): + v = self._body[i][-1] + if not isinstance(v, ignore): + return i, v + return None + + def _previous_item( + self, idx: Optional[int] = None, ignore=(Null,) + ) -> Optional[Item]: + """Find the immediate previous item before index ``idx``. + If ``idx`` is not given, the last item is returned. + """ + prev = self._previous_item_with_index(idx, ignore) + return prev[-1] if prev else None + -class OutOfOrderTableProxy(dict): - def __init__(self, container, indices): # type: (Container, Tuple) -> None +class OutOfOrderTableProxy(_CustomDict): + def __init__(self, container: Container, indices: Tuple[int]) -> None: self._container = container - self._internal_container = Container(self._container.parsing) + self._internal_container = Container(True) self._tables = [] self._tables_map = {} - self._map = {} for i in indices: - key, item = self._container._body[i] + _, item = self._container._body[i] if isinstance(item, Table): self._tables.append(item) @@ -711,28 +836,23 @@ def __init__(self, container, indices): # type: (Container, Tuple) -> None self._internal_container.append(k, v) self._tables_map[k] = table_idx if k is not None: - super(OutOfOrderTableProxy, self).__setitem__(k.key, v) - else: - self._internal_container.append(key, item) - self._map[key] = i - if key is not None: - super(OutOfOrderTableProxy, self).__setitem__(key.key, item) + dict.__setitem__(self, k.key, v) + + def unwrap(self) -> str: + return self._internal_container.unwrap() @property def value(self): return self._internal_container.value - def __getitem__(self, key): # type: (Union[Key, str]) -> Any + def __getitem__(self, key: Union[Key, str]) -> Any: if key not in self._internal_container: raise NonExistentKey(key) return self._internal_container[key] - def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None - if key in self._map: - idx = self._map[key] - self._container._replace_at(idx, key, item) - elif key in self._tables_map: + def __setitem__(self, key: Union[Key, str], item: Any) -> None: + if key in self._tables_map: table = self._tables[self._tables_map[key]] table[key] = item elif self._tables: @@ -741,60 +861,47 @@ def __setitem__(self, key, item): # type: (Union[Key, str], Any) -> None else: self._container[key] = item + self._internal_container[key] = item if key is not None: - super(OutOfOrderTableProxy, self).__setitem__(key, item) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - if key in self._map: - idx = self._map[key] - del self._container[key] - del self._map[key] - elif key in self._tables_map: + dict.__setitem__(self, key, item) + + def _remove_table(self, table: Table) -> None: + """Remove table from the parent container""" + self._tables.remove(table) + for idx, item in enumerate(self._container._body): + if item[1] is table: + self._container._remove_at(idx) + break + + def __delitem__(self, key: Union[Key, str]) -> None: + if key in self._tables_map: table = self._tables[self._tables_map[key]] del table[key] + if not table and len(self._tables) > 1: + self._remove_table(table) del self._tables_map[key] else: raise NonExistentKey(key) del self._internal_container[key] + if key is not None: + dict.__delitem__(self, key) - def keys(self): - return self._internal_container.keys() - - def values(self): - return self._internal_container.values() - - def items(self): # type: () -> Generator[Item] - return self._internal_container.items() - - def update(self, other): # type: (Dict) -> None - self._internal_container.update(other) - - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._internal_container.get(key, default=default) - - def pop(self, key, default=_NOT_SET): - return self._internal_container.pop(key, default=default) - - def setdefault( - self, key, default=None - ): # type: (Union[Key, str], Any) -> Union[Item, Container] - return self._internal_container.setdefault(key, default=default) - - def __contains__(self, key): - return key in self._internal_container - - def __str__(self): - return str(self._internal_container) + def __iter__(self) -> Iterator[str]: + return iter(dict.keys(self)) - def __repr__(self): - return repr(self._internal_container) + def __len__(self) -> int: + return dict.__len__(self) - def __eq__(self, other): # type: (Dict) -> bool - if not isinstance(other, dict): - return NotImplemented + def setdefault(self, key: Union[Key, str], default: Any) -> Any: + super().setdefault(key, default=default) + return self[key] - return self._internal_container == other - def __getattr__(self, attribute): - return getattr(self._internal_container, attribute) +def ends_with_whitespace(it: Any) -> bool: + """Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object + ending with a ``Whitespace``. + """ + return ( + isinstance(it, Table) and isinstance(it.value._previous_item(), Whitespace) + ) or (isinstance(it, AoT) and len(it) > 0 and isinstance(it[-1], Whitespace)) diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/exceptions.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/exceptions.py index 448363638..3147ca2a2 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/exceptions.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/exceptions.py @@ -1,3 +1,4 @@ +from typing import Collection from typing import Optional @@ -13,18 +14,14 @@ class ParseError(ValueError, TOMLKitError): location within the line where the error was encountered. """ - def __init__( - self, line, col, message=None - ): # type: (int, int, Optional[str]) -> None + def __init__(self, line: int, col: int, message: Optional[str] = None) -> None: self._line = line self._col = col if message is None: message = "TOML parse error" - super(ParseError, self).__init__( - "{} at line {} col {}".format(message, self._line, self._col) - ) + super().__init__(f"{message} at line {self._line} col {self._col}") @property def line(self): @@ -40,10 +37,10 @@ class MixedArrayTypesError(ParseError): An array was found that had two or more element types. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Mixed types found in array" - super(MixedArrayTypesError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidNumberError(ParseError): @@ -51,10 +48,10 @@ class InvalidNumberError(ParseError): A numeric field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid number" - super(InvalidNumberError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidDateTimeError(ParseError): @@ -62,10 +59,10 @@ class InvalidDateTimeError(ParseError): A datetime field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid datetime" - super(InvalidDateTimeError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidDateError(ParseError): @@ -73,10 +70,10 @@ class InvalidDateError(ParseError): A date field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid date" - super(InvalidDateError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidTimeError(ParseError): @@ -84,10 +81,10 @@ class InvalidTimeError(ParseError): A date field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid time" - super(InvalidTimeError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidNumberOrDateError(ParseError): @@ -95,10 +92,10 @@ class InvalidNumberOrDateError(ParseError): A numeric or date field was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid number or date format" - super(InvalidNumberOrDateError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidUnicodeValueError(ParseError): @@ -106,10 +103,10 @@ class InvalidUnicodeValueError(ParseError): A unicode code was improperly specified. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Invalid unicode value" - super(InvalidUnicodeValueError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class UnexpectedCharError(ParseError): @@ -117,10 +114,10 @@ class UnexpectedCharError(ParseError): An unexpected character was found during parsing. """ - def __init__(self, line, col, char): # type: (int, int, str) -> None - message = "Unexpected character: {}".format(repr(char)) + def __init__(self, line: int, col: int, char: str) -> None: + message = f"Unexpected character: {repr(char)}" - super(UnexpectedCharError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class EmptyKeyError(ParseError): @@ -128,10 +125,10 @@ class EmptyKeyError(ParseError): An empty key was found during parsing. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Empty key" - super(EmptyKeyError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class EmptyTableNameError(ParseError): @@ -139,10 +136,10 @@ class EmptyTableNameError(ParseError): An empty table name was found during parsing. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Empty table name" - super(EmptyTableNameError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InvalidCharInStringError(ParseError): @@ -150,10 +147,10 @@ class InvalidCharInStringError(ParseError): The string being parsed contains an invalid character. """ - def __init__(self, line, col, char): # type: (int, int, str) -> None - message = "Invalid character {} in string".format(repr(char)) + def __init__(self, line: int, col: int, char: str) -> None: + message = f"Invalid character {repr(char)} in string" - super(InvalidCharInStringError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class UnexpectedEofError(ParseError): @@ -161,10 +158,10 @@ class UnexpectedEofError(ParseError): The TOML being parsed ended before the end of a statement. """ - def __init__(self, line, col): # type: (int, int) -> None + def __init__(self, line: int, col: int) -> None: message = "Unexpected end of file" - super(UnexpectedEofError, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) class InternalParserError(ParseError): @@ -172,14 +169,12 @@ class InternalParserError(ParseError): An error that indicates a bug in the parser. """ - def __init__( - self, line, col, message=None - ): # type: (int, int, Optional[str]) -> None + def __init__(self, line: int, col: int, message: Optional[str] = None) -> None: msg = "Internal parser error" if message: - msg += " ({})".format(message) + msg += f" ({message})" - super(InternalParserError, self).__init__(line, col, message=msg) + super().__init__(line, col, message=msg) class NonExistentKey(KeyError, TOMLKitError): @@ -188,9 +183,9 @@ class NonExistentKey(KeyError, TOMLKitError): """ def __init__(self, key): - message = 'Key "{}" does not exist.'.format(key) + message = f'Key "{key}" does not exist.' - super(NonExistentKey, self).__init__(message) + super().__init__(message) class KeyAlreadyPresent(TOMLKitError): @@ -199,23 +194,34 @@ class KeyAlreadyPresent(TOMLKitError): """ def __init__(self, key): - message = 'Key "{}" already exists.'.format(key) + key = getattr(key, "key", key) + message = f'Key "{key}" already exists.' - super(KeyAlreadyPresent, self).__init__(message) + super().__init__(message) class InvalidControlChar(ParseError): - def __init__(self, line, col, char, type): # type: (int, int, int, str) -> None + def __init__(self, line: int, col: int, char: int, type: str) -> None: display_code = "\\u00" if char < 16: display_code += "0" - display_code += str(char) + display_code += hex(char)[2:] message = ( - "Control characters (codes less than 0x1f and 0x7f) are not allowed in {}, " - "use {} instead".format(type, display_code) + "Control characters (codes less than 0x1f and 0x7f)" + f" are not allowed in {type}, " + f"use {display_code} instead" ) - super(InvalidControlChar, self).__init__(line, col, message=message) + super().__init__(line, col, message=message) + + +class InvalidStringError(ValueError, TOMLKitError): + def __init__(self, value: str, invalid_sequences: Collection[str], delimiter: str): + repr_ = repr(value)[1:-1] + super().__init__( + f"Invalid string: {delimiter}{repr_}{delimiter}. " + f"The character sequences {invalid_sequences} are invalid." + ) diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/items.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/items.py index 184ffe7da..77fa27d3f 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/items.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/items.py @@ -1,35 +1,164 @@ -from __future__ import unicode_literals - +import abc +import copy import re import string from datetime import date from datetime import datetime from datetime import time +from datetime import tzinfo from enum import Enum +from typing import TYPE_CHECKING from typing import Any +from typing import Collection from typing import Dict -from typing import Generator +from typing import Iterable +from typing import Iterator from typing import List from typing import Optional +from typing import Sequence +from typing import TypeVar from typing import Union +from typing import cast +from typing import overload + +from tomlkit._compat import PY38 +from tomlkit._compat import decode +from tomlkit._utils import CONTROL_CHARS +from tomlkit._utils import escape_string +from tomlkit.exceptions import InvalidStringError + + +if TYPE_CHECKING: # pragma: no cover + # Define _CustomList and _CustomDict as a workaround for: + # https://github.com/python/mypy/issues/11427 + # + # According to this issue, the typeshed contains a "lie" + # (it adds MutableSequence to the ancestry of list and MutableMapping to + # the ancestry of dict) which completely messes with the type inference for + # Table, InlineTable, Array and Container. + # + # Importing from builtins is preferred over simple assignment, see issues: + # https://github.com/python/mypy/issues/8715 + # https://github.com/python/mypy/issues/10068 + from builtins import dict as _CustomDict # noqa: N812, TC004 + from builtins import list as _CustomList # noqa: N812, TC004 + + # Allow type annotations but break circular imports + from tomlkit import container +else: + from collections.abc import MutableMapping + from collections.abc import MutableSequence -from ._compat import PY2 -from ._compat import PY38 -from ._compat import decode -from ._compat import long -from ._compat import unicode -from ._utils import escape_string + class _CustomList(MutableSequence, list): + """Adds MutableSequence mixin while pretending to be a builtin list""" + class _CustomDict(MutableMapping, dict): + """Adds MutableMapping mixin while pretending to be a builtin dict""" -if PY2: - from functools32 import lru_cache -else: - from functools import lru_cache + +ItemT = TypeVar("ItemT", bound="Item") + + +@overload +def item( + value: bool, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Bool": + ... + + +@overload +def item( + value: int, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Integer": + ... + + +@overload +def item( + value: float, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Float": + ... + + +@overload +def item( + value: str, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "String": + ... + + +@overload +def item( + value: datetime, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "DateTime": + ... + + +@overload +def item( + value: date, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Date": + ... + + +@overload +def item( + value: time, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Time": + ... + + +@overload +def item( + value: Sequence[dict], _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "AoT": + ... + + +@overload +def item( + value: Sequence, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Array": + ... -def item(value, _parent=None, _sort_keys=False): - from .container import Container +@overload +def item(value: dict, _parent: "Array" = ..., _sort_keys: bool = ...) -> "InlineTable": + ... + + +@overload +def item( + value: dict, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> "Table": + ... + + +@overload +def item( + value: ItemT, _parent: Optional["Item"] = ..., _sort_keys: bool = ... +) -> ItemT: + ... + + +def item( + value: Any, _parent: Optional["Item"] = None, _sort_keys: bool = False +) -> "Item": + """Create a TOML item from a Python object. + + :Example: + + >>> item(42) + 42 + >>> item([1, 2, 3]) + [1, 2, 3] + >>> item({'a': 1, 'b': 2}) + a = 1 + b = 2 + """ + + from tomlkit.container import Container if isinstance(value, Item): return value @@ -41,7 +170,10 @@ def item(value, _parent=None, _sort_keys=False): elif isinstance(value, float): return Float(value, Trivia(), str(value)) elif isinstance(value, dict): - val = Table(Container(), Trivia(), False) + table_constructor = ( + InlineTable if isinstance(_parent, (Array, InlineTable)) else Table + ) + val = table_constructor(Container(), Trivia(), False) for k, v in sorted( value.items(), key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), @@ -49,35 +181,39 @@ def item(value, _parent=None, _sort_keys=False): val[k] = item(v, _parent=val, _sort_keys=_sort_keys) return val - elif isinstance(value, list): - if value and isinstance(value[0], dict): + elif isinstance(value, (list, tuple)): + if ( + value + and all(isinstance(v, dict) for v in value) + and (_parent is None or isinstance(_parent, Table)) + ): a = AoT([]) + table_constructor = Table else: a = Array([], Trivia()) + table_constructor = InlineTable for v in value: if isinstance(v, dict): - table = Table(Container(), Trivia(), True) + table = table_constructor(Container(), Trivia(), True) for k, _v in sorted( v.items(), key=lambda i: (isinstance(i[1], dict), i[0] if _sort_keys else 1), ): - i = item(_v, _sort_keys=_sort_keys) + i = item(_v, _parent=table, _sort_keys=_sort_keys) if isinstance(table, InlineTable): i.trivia.trail = "" - table[k] = item(i, _sort_keys=_sort_keys) + table[k] = i v = table a.append(v) return a - elif isinstance(value, (str, unicode)): - escaped = escape_string(value) - - return String(StringType.SLB, decode(value), escaped, Trivia()) + elif isinstance(value, str): + return String.from_raw(value) elif isinstance(value, datetime): return DateTime( value.year, @@ -104,7 +240,7 @@ def item(value, _parent=None, _sort_keys=False): value.isoformat(), ) - raise ValueError("Invalid type {}".format(type(value))) + raise ValueError(f"Invalid type {type(value)}") class StringType(Enum): @@ -117,29 +253,56 @@ class StringType(Enum): # Multi Line Literal MLL = "'''" + @classmethod + def select(cls, literal=False, multiline=False) -> "StringType": + return { + (False, False): cls.SLB, + (False, True): cls.MLB, + (True, False): cls.SLL, + (True, True): cls.MLL, + }[(literal, multiline)] + + @property + def escaped_sequences(self) -> Collection[str]: + # https://toml.io/en/v1.0.0#string + escaped_in_basic = CONTROL_CHARS | {"\\"} + allowed_in_multiline = {"\n", "\r"} + return { + StringType.SLB: escaped_in_basic | {'"'}, + StringType.MLB: (escaped_in_basic | {'"""'}) - allowed_in_multiline, + StringType.SLL: (), + StringType.MLL: (), + }[self] + + @property + def invalid_sequences(self) -> Collection[str]: + # https://toml.io/en/v1.0.0#string + forbidden_in_literal = CONTROL_CHARS - {"\t"} + allowed_in_multiline = {"\n", "\r"} + return { + StringType.SLB: (), + StringType.MLB: (), + StringType.SLL: forbidden_in_literal | {"'"}, + StringType.MLL: (forbidden_in_literal | {"'''"}) - allowed_in_multiline, + }[self] + @property - @lru_cache(maxsize=None) - def unit(self): # type: () -> str + def unit(self) -> str: return self.value[0] - @lru_cache(maxsize=None) - def is_basic(self): # type: () -> bool + def is_basic(self) -> bool: return self in {StringType.SLB, StringType.MLB} - @lru_cache(maxsize=None) - def is_literal(self): # type: () -> bool + def is_literal(self) -> bool: return self in {StringType.SLL, StringType.MLL} - @lru_cache(maxsize=None) - def is_singleline(self): # type: () -> bool + def is_singleline(self) -> bool: return self in {StringType.SLB, StringType.SLL} - @lru_cache(maxsize=None) - def is_multiline(self): # type: () -> bool + def is_multiline(self) -> bool: return self in {StringType.MLB, StringType.MLL} - @lru_cache(maxsize=None) - def toggle(self): # type: () -> StringType + def toggle(self) -> "StringType": return { StringType.SLB: StringType.MLB, StringType.MLB: StringType.SLB, @@ -152,13 +315,9 @@ class BoolType(Enum): TRUE = "true" FALSE = "false" - @lru_cache(maxsize=None) def __bool__(self): return {BoolType.TRUE: True, BoolType.FALSE: False}[self] - if PY2: - __nonzero__ = __bool__ # for PY2 - def __iter__(self): return iter(self.value) @@ -172,8 +331,12 @@ class Trivia: """ def __init__( - self, indent=None, comment_ws=None, comment=None, trail=None - ): # type: (str, str, str, str) -> None + self, + indent: str = None, + comment_ws: str = None, + comment: str = None, + trail: str = None, + ) -> None: # Whitespace before a value. self.indent = indent or "" # Whitespace after a value, but before a comment. @@ -186,6 +349,9 @@ def __init__( self.trail = trail + def copy(self) -> "Trivia": + return type(self)(self.indent, self.comment_ws, self.comment, self.trail) + class KeyType(Enum): """ @@ -200,17 +366,63 @@ class KeyType(Enum): Literal = "'" -class Key: - """ - A key value. - """ +class Key(abc.ABC): + """Base class for a key""" + + sep: str + _original: str + _keys: List["SingleKey"] + _dotted: bool + key: str + + @abc.abstractmethod + def __hash__(self) -> int: + pass + + @abc.abstractmethod + def __eq__(self, __o: object) -> bool: + pass + + def is_dotted(self) -> bool: + """If the key is followed by other keys""" + return self._dotted + + def __iter__(self) -> Iterator["SingleKey"]: + return iter(self._keys) + + def concat(self, other: "Key") -> "DottedKey": + """Concatenate keys into a dotted key""" + keys = self._keys + other._keys + return DottedKey(keys, sep=self.sep) + + def is_multi(self) -> bool: + """Check if the key contains multiple keys""" + return len(self._keys) > 1 + + def as_string(self) -> str: + """The TOML representation""" + return self._original + + def __str__(self) -> str: + return self.as_string() + + def __repr__(self) -> str: + return f"" + + +class SingleKey(Key): + """A single key""" def __init__( - self, k, t=None, sep=None, dotted=False, original=None - ): # type: (str, Optional[KeyType], Optional[str], bool, Optional[str]) -> None + self, + k: str, + t: Optional[KeyType] = None, + sep: Optional[str] = None, + original: Optional[str] = None, + ) -> None: if t is None: - if any( - [c not in string.ascii_letters + string.digits + "-" + "_" for c in k] + if not k or any( + c not in string.ascii_letters + string.digits + "-" + "_" for c in k ): t = KeyType.Basic else: @@ -223,63 +435,88 @@ def __init__( self.sep = sep self.key = k if original is None: - original = k + key_str = escape_string(k) if t == KeyType.Basic else k + original = f"{t.value}{key_str}{t.value}" self._original = original - - self._dotted = dotted + self._keys = [self] + self._dotted = False @property - def delimiter(self): # type: () -> str + def delimiter(self) -> str: + """The delimiter: double quote/single quote/none""" return self.t.value - def is_dotted(self): # type: () -> bool - return self._dotted - - def is_bare(self): # type: () -> bool + def is_bare(self) -> bool: + """Check if the key is bare""" return self.t == KeyType.Bare - def as_string(self): # type: () -> str - return "{}{}{}".format(self.delimiter, self._original, self.delimiter) - - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(self.key) - def __eq__(self, other): # type: (Key) -> bool + def __eq__(self, other: Any) -> bool: if isinstance(other, Key): - return self.key == other.key + return isinstance(other, SingleKey) and self.key == other.key return self.key == other - def __str__(self): # type: () -> str - return self.as_string() - def __repr__(self): # type: () -> str - return "".format(self.as_string()) +class DottedKey(Key): + def __init__( + self, + keys: Iterable[Key], + sep: Optional[str] = None, + original: Optional[str] = None, + ) -> None: + self._keys = list(keys) + if original is None: + original = ".".join(k.as_string() for k in self._keys) + + self.sep = " = " if sep is None else sep + self._original = original + self._dotted = False + self.key = ".".join(k.key for k in self._keys) + + def __hash__(self) -> int: + return hash(tuple(self._keys)) + + def __eq__(self, __o: object) -> bool: + return isinstance(__o, DottedKey) and self._keys == __o._keys -class Item(object): +class Item: """ An item within a TOML document. """ - def __init__(self, trivia): # type: (Trivia) -> None + def __init__(self, trivia: Trivia) -> None: self._trivia = trivia @property - def trivia(self): # type: () -> Trivia + def trivia(self) -> Trivia: + """The trivia element associated with this item""" return self._trivia @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: raise NotImplementedError() - def as_string(self): # type: () -> str + def as_string(self) -> str: + """The TOML representation""" + raise NotImplementedError() + + @property + def value(self) -> Any: + return self + + def unwrap(self) -> Any: + """Returns as pure python object (ppo)""" raise NotImplementedError() # Helpers - def comment(self, comment): # type: (str) -> Item + def comment(self, comment: str) -> "Item": + """Attach a comment to this item""" if not comment.strip().startswith("#"): comment = "# " + comment @@ -288,7 +525,8 @@ def comment(self, comment): # type: (str) -> Item return self - def indent(self, indent): # type: (int) -> Item + def indent(self, indent: int) -> "Item": + """Indent this item with given number of spaces""" if self._trivia.indent.startswith("\n"): self._trivia.indent = "\n" + " " * indent else: @@ -296,16 +534,16 @@ def indent(self, indent): # type: (int) -> Item return self - def is_boolean(self): # type: () -> bool + def is_boolean(self) -> bool: return isinstance(self, Bool) - def is_table(self): # type: () -> bool + def is_table(self) -> bool: return isinstance(self, Table) - def is_inline_table(self): # type: () -> bool + def is_inline_table(self) -> bool: return isinstance(self, InlineTable) - def is_aot(self): # type: () -> bool + def is_aot(self) -> bool: return isinstance(self, AoT) def _getstate(self, protocol=3): @@ -323,34 +561,36 @@ class Whitespace(Item): A whitespace literal. """ - def __init__(self, s, fixed=False): # type: (str, bool) -> None + def __init__(self, s: str, fixed: bool = False) -> None: self._s = s self._fixed = fixed @property - def s(self): # type: () -> str + def s(self) -> str: return self._s @property - def value(self): # type: () -> str + def value(self) -> str: + """The wrapped string of the whitespace""" return self._s @property - def trivia(self): # type: () -> Trivia + def trivia(self) -> Trivia: raise RuntimeError("Called trivia on a Whitespace variant.") @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 0 - def is_fixed(self): # type: () -> bool + def is_fixed(self) -> bool: + """If the whitespace is fixed, it can't be merged or discarded from the output.""" return self._fixed - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._s - def __repr__(self): # type: () -> str - return "<{} {}>".format(self.__class__.__name__, repr(self._s)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {repr(self._s)}>" def _getstate(self, protocol=3): return self._s, self._fixed @@ -362,28 +602,28 @@ class Comment(Item): """ @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 1 - def as_string(self): # type: () -> str - return "{}{}{}".format( - self._trivia.indent, decode(self._trivia.comment), self._trivia.trail + def as_string(self) -> str: + return ( + f"{self._trivia.indent}{decode(self._trivia.comment)}{self._trivia.trail}" ) - def __str__(self): # type: () -> str - return "{}{}".format(self._trivia.indent, decode(self._trivia.comment)) + def __str__(self) -> str: + return f"{self._trivia.indent}{decode(self._trivia.comment)}" -class Integer(long, Item): +class Integer(int, Item): """ An integer literal. """ - def __new__(cls, value, trivia, raw): # type: (int, Trivia, str) -> Integer - return super(Integer, cls).__new__(cls, value) + def __new__(cls, value: int, trivia: Trivia, raw: str) -> "Integer": + return super().__new__(cls, value) - def __init__(self, _, trivia, raw): # type: (int, Trivia, str) -> None - super(Integer, self).__init__(trivia) + def __init__(self, _: int, trivia: Trivia, raw: str) -> None: + super().__init__(trivia) self._raw = raw self._sign = False @@ -391,24 +631,26 @@ def __init__(self, _, trivia, raw): # type: (int, Trivia, str) -> None if re.match(r"^[+\-]\d+$", raw): self._sign = True + def unwrap(self) -> int: + return int(self) + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 2 @property - def value(self): # type: () -> int + def value(self) -> int: + """The wrapped integer value""" return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): - result = super(Integer, self).__add__(other) - - return self._new(result) + return self._new(int(self._raw) + other) def __radd__(self, other): - result = super(Integer, self).__radd__(other) + result = super().__radd__(other) if isinstance(other, Integer): return self._new(result) @@ -416,12 +658,12 @@ def __radd__(self, other): return result def __sub__(self, other): - result = super(Integer, self).__sub__(other) + result = super().__sub__(other) return self._new(result) def __rsub__(self, other): - result = super(Integer, self).__rsub__(other) + result = super().__rsub__(other) if isinstance(other, Integer): return self._new(result) @@ -430,7 +672,6 @@ def __rsub__(self, other): def _new(self, result): raw = str(result) - if self._sign: sign = "+" if result >= 0 else "-" raw = sign + raw @@ -446,11 +687,11 @@ class Float(float, Item): A float literal. """ - def __new__(cls, value, trivia, raw): # type: (float, Trivia, str) -> Integer - return super(Float, cls).__new__(cls, value) + def __new__(cls, value: float, trivia: Trivia, raw: str) -> Integer: + return super().__new__(cls, value) - def __init__(self, _, trivia, raw): # type: (float, Trivia, str) -> None - super(Float, self).__init__(trivia) + def __init__(self, _: float, trivia: Trivia, raw: str) -> None: + super().__init__(trivia) self._raw = raw self._sign = False @@ -458,24 +699,28 @@ def __init__(self, _, trivia, raw): # type: (float, Trivia, str) -> None if re.match(r"^[+\-].+$", raw): self._sign = True + def unwrap(self) -> float: + return float(self) + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 3 @property - def value(self): # type: () -> float + def value(self) -> float: + """The wrapped float value""" return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): - result = super(Float, self).__add__(other) + result = super().__add__(other) return self._new(result) def __radd__(self, other): - result = super(Float, self).__radd__(other) + result = super().__radd__(other) if isinstance(other, Float): return self._new(result) @@ -483,12 +728,12 @@ def __radd__(self, other): return result def __sub__(self, other): - result = super(Float, self).__sub__(other) + result = super().__sub__(other) return self._new(result) def __rsub__(self, other): - result = super(Float, self).__rsub__(other) + result = super().__rsub__(other) if isinstance(other, Float): return self._new(result) @@ -513,20 +758,24 @@ class Bool(Item): A boolean literal. """ - def __init__(self, t, trivia): # type: (int, Trivia) -> None - super(Bool, self).__init__(trivia) + def __init__(self, t: int, trivia: Trivia) -> None: + super().__init__(trivia) self._value = bool(t) + def unwrap(self) -> bool: + return bool(self) + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 4 @property - def value(self): # type: () -> bool + def value(self) -> bool: + """The wrapped boolean value""" return self._value - def as_string(self): # type: () -> str + def as_string(self) -> str: return str(self._value).lower() def _getstate(self, protocol=3): @@ -557,18 +806,17 @@ class DateTime(Item, datetime): def __new__( cls, - year, - month, - day, - hour, - minute, - second, - microsecond, - tzinfo, - trivia, - raw, - **kwargs - ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str, Any) -> datetime + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + *_: Any, + **kwargs: Any, + ) -> datetime: return datetime.__new__( cls, year, @@ -579,25 +827,51 @@ def __new__( second, microsecond, tzinfo=tzinfo, - **kwargs + **kwargs, ) def __init__( - self, year, month, day, hour, minute, second, microsecond, tzinfo, trivia, raw - ): # type: (int, int, int, int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None - super(DateTime, self).__init__(trivia) - - self._raw = raw + self, + year: int, + month: int, + day: int, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Optional[Trivia] = None, + raw: Optional[str] = None, + **kwargs: Any, + ) -> None: + super().__init__(trivia or Trivia()) + + self._raw = raw or self.isoformat() + + def unwrap(self) -> datetime: + ( + year, + month, + day, + hour, + minute, + second, + microsecond, + tzinfo, + _, + _, + ) = self._getstate() + return datetime(year, month, day, hour, minute, second, microsecond, tzinfo) @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 5 @property - def value(self): # type: () -> datetime + def value(self) -> datetime: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): @@ -613,7 +887,7 @@ def __add__(self, other): self.tzinfo, ).__add__(other) else: - result = super(DateTime, self).__add__(other) + result = super().__add__(other) return self._new(result) @@ -630,14 +904,23 @@ def __sub__(self, other): self.tzinfo, ).__sub__(other) else: - result = super(DateTime, self).__sub__(other) + result = super().__sub__(other) if isinstance(result, datetime): result = self._new(result) return result - def _new(self, result): + def replace(self, *args: Any, **kwargs: Any) -> datetime: + return self._new(super().replace(*args, **kwargs)) + + def astimezone(self, tz: tzinfo) -> datetime: + result = super().astimezone(tz) + if PY38: + return result + return self._new(result) + + def _new(self, result) -> "DateTime": raw = result.isoformat() return DateTime( @@ -673,32 +956,36 @@ class Date(Item, date): A date literal. """ - def __new__(cls, year, month, day, *_): # type: (int, int, int, Any) -> date + def __new__(cls, year: int, month: int, day: int, *_: Any) -> date: return date.__new__(cls, year, month, day) def __init__( - self, year, month, day, trivia, raw - ): # type: (int, int, int, Trivia, str) -> None - super(Date, self).__init__(trivia) + self, year: int, month: int, day: int, trivia: Trivia, raw: str + ) -> None: + super().__init__(trivia) self._raw = raw + def unwrap(self) -> date: + (year, month, day, _, _) = self._getstate() + return date(year, month, day) + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 6 @property - def value(self): # type: () -> date + def value(self) -> date: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw def __add__(self, other): if PY38: result = date(self.year, self.month, self.day).__add__(other) else: - result = super(Date, self).__add__(other) + result = super().__add__(other) return self._new(result) @@ -706,13 +993,16 @@ def __sub__(self, other): if PY38: result = date(self.year, self.month, self.day).__sub__(other) else: - result = super(Date, self).__sub__(other) + result = super().__sub__(other) if isinstance(result, date): result = self._new(result) return result + def replace(self, *args: Any, **kwargs: Any) -> date: + return self._new(super().replace(*args, **kwargs)) + def _new(self, result): raw = result.isoformat() @@ -728,29 +1018,62 @@ class Time(Item, time): """ def __new__( - cls, hour, minute, second, microsecond, tzinfo, *_ - ): # type: (int, int, int, int, Optional[datetime.tzinfo], Any) -> time + cls, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + *_: Any, + ) -> time: return time.__new__(cls, hour, minute, second, microsecond, tzinfo) def __init__( - self, hour, minute, second, microsecond, tzinfo, trivia, raw - ): # type: (int, int, int, int, Optional[datetime.tzinfo], Trivia, str) -> None - super(Time, self).__init__(trivia) + self, + hour: int, + minute: int, + second: int, + microsecond: int, + tzinfo: Optional[tzinfo], + trivia: Trivia, + raw: str, + ) -> None: + super().__init__(trivia) self._raw = raw + def unwrap(self) -> time: + (hour, minute, second, microsecond, tzinfo, _, _) = self._getstate() + return time(hour, minute, second, microsecond, tzinfo) + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 7 @property - def value(self): # type: () -> time + def value(self) -> time: return self - def as_string(self): # type: () -> str + def as_string(self) -> str: return self._raw - def _getstate(self, protocol=3): + def replace(self, *args: Any, **kwargs: Any) -> time: + return self._new(super().replace(*args, **kwargs)) + + def _new(self, result): + raw = result.isoformat() + + return Time( + result.hour, + result.minute, + result.second, + result.microsecond, + result.tzinfo, + self._trivia, + raw, + ) + + def _getstate(self, protocol: int = 3) -> tuple: return ( self.hour, self.minute, @@ -762,171 +1085,512 @@ def _getstate(self, protocol=3): ) -class Array(Item, list): +class _ArrayItemGroup: + __slots__ = ("value", "indent", "comma", "comment") + + def __init__( + self, + value: Optional[Item] = None, + indent: Optional[Whitespace] = None, + comma: Optional[Whitespace] = None, + comment: Optional[Comment] = None, + ) -> None: + self.value = value + self.indent = indent + self.comma = comma + self.comment = comment + + def __iter__(self) -> Iterator[Item]: + return filter( + lambda x: x is not None, (self.indent, self.value, self.comma, self.comment) + ) + + def __repr__(self) -> str: + return repr(tuple(self)) + + def is_whitespace(self) -> bool: + return self.value is None and self.comment is None + + def __bool__(self) -> bool: + try: + next(iter(self)) + except StopIteration: + return False + return True + + +class Array(Item, _CustomList): """ An array literal """ def __init__( - self, value, trivia, multiline=False - ): # type: (list, Trivia, bool) -> None - super(Array, self).__init__(trivia) - + self, value: List[Item], trivia: Trivia, multiline: bool = False + ) -> None: + super().__init__(trivia) list.__init__( - self, [v.value for v in value if not isinstance(v, (Whitespace, Comment))] + self, + [v.value for v in value if not isinstance(v, (Whitespace, Comment, Null))], ) - - self._value = value + self._index_map: Dict[int, int] = {} + self._value = self._group_values(value) self._multiline = multiline + self._reindex() + + def _group_values(self, value: List[Item]) -> List[_ArrayItemGroup]: + """Group the values into (indent, value, comma, comment) tuples""" + groups = [] + this_group = _ArrayItemGroup() + for item in value: + if isinstance(item, Whitespace): + if "," not in item.s: + groups.append(this_group) + this_group = _ArrayItemGroup(indent=item) + else: + if this_group.value is None: + # when comma is met and no value is provided, add a dummy Null + this_group.value = Null() + this_group.comma = item + elif isinstance(item, Comment): + if this_group.value is None: + this_group.value = Null() + this_group.comment = item + elif this_group.value is None: + this_group.value = item + else: + groups.append(this_group) + this_group = _ArrayItemGroup(value=item) + groups.append(this_group) + return [group for group in groups if group] + + def unwrap(self) -> List[Any]: + unwrapped = [] + for v in self: + if isinstance(v, Item): + unwrapped.append(v.unwrap()) + else: + unwrapped.append(v) + return unwrapped @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 8 @property - def value(self): # type: () -> list + def value(self) -> list: return self - def multiline(self, multiline): # type: (bool) -> self + def _iter_items(self) -> Iterator[Item]: + for v in self._value: + yield from v + + def multiline(self, multiline: bool) -> "Array": + """Change the array to display in multiline or not. + + :Example: + + >>> a = item([1, 2, 3]) + >>> print(a.as_string()) + [1, 2, 3] + >>> print(a.multiline(True).as_string()) + [ + 1, + 2, + 3, + ] + """ self._multiline = multiline return self - def as_string(self): # type: () -> str - if not self._multiline: - return "[{}]".format("".join(v.as_string() for v in self._value)) - - s = "[\n" + self.trivia.indent + " " * 4 - s += (",\n" + self.trivia.indent + " " * 4).join( - v.as_string() for v in self._value if not isinstance(v, Whitespace) + def as_string(self) -> str: + if not self._multiline or not self._value: + return f'[{"".join(v.as_string() for v in self._iter_items())}]' + + s = "[\n" + s += "".join( + self.trivia.indent + + " " * 4 + + v.value.as_string() + + ("," if not isinstance(v.value, Null) else "") + + (v.comment.as_string() if v.comment is not None else "") + + "\n" + for v in self._value + if v.value is not None ) - s += ",\n" - s += "]" + s += self.trivia.indent + "]" return s - def append(self, _item): # type: (Any) -> None - if self._value: - self._value.append(Whitespace(", ")) + def _reindex(self) -> None: + self._index_map.clear() + index = 0 + for i, v in enumerate(self._value): + if v.value is None or isinstance(v.value, Null): + continue + self._index_map[index] = i + index += 1 + + def add_line( + self, + *items: Any, + indent: str = " ", + comment: Optional[str] = None, + add_comma: bool = True, + newline: bool = True, + ) -> None: + """Add multiple items in a line to control the format precisely. + When add_comma is True, only accept actual values and + ", " will be added between values automatically. + + :Example: + + >>> a = array() + >>> a.add_line(1, 2, 3) + >>> a.add_line(4, 5, 6) + >>> a.add_line(indent="") + >>> print(a.as_string()) + [ + 1, 2, 3, + 4, 5, 6, + ] + """ + new_values: List[Item] = [] + first_indent = f"\n{indent}" if newline else indent + if first_indent: + new_values.append(Whitespace(first_indent)) + whitespace = "" + data_values = [] + for i, el in enumerate(items): + it = item(el, _parent=self) + if isinstance(it, Comment) or add_comma and isinstance(el, Whitespace): + raise ValueError(f"item type {type(it)} is not allowed in add_line") + if not isinstance(it, Whitespace): + if whitespace: + new_values.append(Whitespace(whitespace)) + whitespace = "" + new_values.append(it) + data_values.append(it.value) + if add_comma: + new_values.append(Whitespace(",")) + if i != len(items) - 1: + new_values.append(Whitespace(" ")) + elif "," not in it.s: + whitespace += it.s + else: + new_values.append(it) + if whitespace: + new_values.append(Whitespace(whitespace)) + if comment: + indent = " " if items else "" + new_values.append( + Comment(Trivia(indent=indent, comment=f"# {comment}", trail="")) + ) + list.extend(self, data_values) + if len(self._value) > 0: + last_item = self._value[-1] + last_value_item = next( + ( + v + for v in self._value[::-1] + if v.value is not None and not isinstance(v.value, Null) + ), + None, + ) + if last_value_item is not None: + last_value_item.comma = Whitespace(",") + if last_item.is_whitespace(): + self._value[-1:-1] = self._group_values(new_values) + else: + self._value.extend(self._group_values(new_values)) + else: + self._value.extend(self._group_values(new_values)) + self._reindex() + + def clear(self) -> None: + """Clear the array.""" + list.clear(self) + self._index_map.clear() + self._value.clear() + + def __len__(self) -> int: + return list.__len__(self) + + def __getitem__(self, key: Union[int, slice]) -> Any: + return list.__getitem__(self, key) + + def __setitem__(self, key: Union[int, slice], value: Any) -> Any: + it = item(value, _parent=self) + list.__setitem__(self, key, it.value) + if isinstance(key, slice): + raise ValueError("slice assignment is not supported") + if key < 0: + key += len(self) + self._value[self._index_map[key]].value = it + + def insert(self, pos: int, value: Any) -> None: + it = item(value, _parent=self) + length = len(self) + if not isinstance(it, (Comment, Whitespace)): + list.insert(self, pos, it.value) + if pos < 0: + pos += length + if pos < 0: + pos = 0 + + idx = 0 # insert position of the self._value list + default_indent = " " + if pos < length: + try: + idx = self._index_map[pos] + except KeyError as e: + raise IndexError("list index out of range") from e + else: + idx = len(self._value) + if idx >= 1 and self._value[idx - 1].is_whitespace(): + # The last item is a pure whitespace(\n ), insert before it + idx -= 1 + if ( + self._value[idx].indent is not None + and "\n" in self._value[idx].indent.s + ): + default_indent = "\n " + indent: Optional[Item] = None + comma: Optional[Item] = Whitespace(",") if pos < length else None + if idx < len(self._value) and not self._value[idx].is_whitespace(): + # Prefer to copy the indentation from the item after + indent = self._value[idx].indent + if idx > 0: + last_item = self._value[idx - 1] + if indent is None: + indent = last_item.indent + if not isinstance(last_item.value, Null) and "\n" in default_indent: + # Copy the comma from the last item if 1) it contains a value and + # 2) the array is multiline + comma = last_item.comma + if last_item.comma is None and not isinstance(last_item.value, Null): + # Add comma to the last item to separate it from the following items. + last_item.comma = Whitespace(",") + if indent is None and (idx > 0 or "\n" in default_indent): + # apply default indent if it isn't the first item or the array is multiline. + indent = Whitespace(default_indent) + new_item = _ArrayItemGroup(value=it, indent=indent, comma=comma) + self._value.insert(idx, new_item) + self._reindex() + + def __delitem__(self, key: Union[int, slice]): + length = len(self) + list.__delitem__(self, key) + + if isinstance(key, slice): + indices_to_remove = list( + range(key.start or 0, key.stop or length, key.step or 1) + ) + else: + indices_to_remove = [length + key if key < 0 else key] + for i in sorted(indices_to_remove, reverse=True): + try: + idx = self._index_map[i] + except KeyError as e: + if not isinstance(key, slice): + raise IndexError("list index out of range") from e + else: + del self._value[idx] + if ( + idx == 0 + and len(self._value) > 0 + and "\n" not in self._value[idx].indent.s + ): + # Remove the indentation of the first item if not newline + self._value[idx].indent = None + if len(self._value) > 0: + v = self._value[-1] + if not v.is_whitespace(): + # remove the comma of the last item + v.comma = None + + self._reindex() - it = item(_item) - super(Array, self).append(it.value) + def __str__(self): + return str([v.value.value for v in self._iter_items() if v.value is not None]) - self._value.append(it) + def _getstate(self, protocol=3): + return list(self._iter_items()), self._trivia, self._multiline - if not PY2: - def clear(self): - super(Array, self).clear() +AT = TypeVar("AT", bound="AbstractTable") - self._value.clear() - def __iadd__(self, other): # type: (list) -> Array - if not isinstance(other, list): - return NotImplemented +class AbstractTable(Item, _CustomDict): + """Common behaviour of both :class:`Table` and :class:`InlineTable`""" - for v in other: - self.append(v) + def __init__(self, value: "container.Container", trivia: Trivia): + Item.__init__(self, trivia) - return self + self._value = value - def __delitem__(self, key): - super(Array, self).__delitem__(key) + for k, v in self._value.body: + if k is not None: + dict.__setitem__(self, k.key, v) - j = 0 if key >= 0 else -1 - for i, v in enumerate(self._value if key >= 0 else reversed(self._value)): - if key < 0: - i = -i - 1 + def unwrap(self) -> Dict[str, Any]: + unwrapped = {} + for k, v in self.items(): + if isinstance(k, Key): + k = k.key + if isinstance(v, Item): + v = v.unwrap() + unwrapped[k] = v - if isinstance(v, (Comment, Whitespace)): - continue + return unwrapped - if j == key: - del self._value[i] + @property + def value(self) -> "container.Container": + return self._value - if i < 0 and abs(i) > len(self._value): - i += 1 + @overload + def append(self: AT, key: None, value: Union[Comment, Whitespace]) -> AT: + ... - if i < len(self._value) - 1 and isinstance(self._value[i], Whitespace): - del self._value[i] + @overload + def append(self: AT, key: Union[Key, str], value: Any) -> AT: + ... + + def append(self, key, value): + raise NotImplementedError + + @overload + def add(self: AT, value: Union[Comment, Whitespace]) -> AT: + ... + + @overload + def add(self: AT, key: Union[Key, str], value: Any) -> AT: + ... + + def add(self, key, value=None): + if value is None: + if not isinstance(key, (Comment, Whitespace)): + msg = "Non comment/whitespace items must have an associated key" + raise ValueError(msg) - break + key, value = None, key - j += 1 if key >= 0 else -1 + return self.append(key, value) + + def remove(self: AT, key: Union[Key, str]) -> AT: + self._value.remove(key) + + if isinstance(key, Key): + key = key.key + + if key is not None: + dict.__delitem__(self, key) + + return self + + def setdefault(self, key: Union[Key, str], default: Any) -> Any: + super().setdefault(key, default) + return self[key] def __str__(self): - return str( - [v.value for v in self._value if not isinstance(v, (Whitespace, Comment))] - ) + return str(self.value) - def __repr__(self): - return str(self) + def copy(self: AT) -> AT: + return copy.copy(self) - def _getstate(self, protocol=3): - return self._value, self._trivia + def __repr__(self) -> str: + return repr(self.value) + + def __iter__(self) -> Iterator[str]: + return iter(self._value) + + def __len__(self) -> int: + return len(self._value) + + def __delitem__(self, key: Union[Key, str]) -> None: + self.remove(key) + + def __getitem__(self, key: Union[Key, str]) -> Item: + return cast(Item, self._value[key]) + + def __setitem__(self, key: Union[Key, str], value: Any) -> None: + if not isinstance(value, Item): + value = item(value, _parent=self) + + is_replace = key in self + self._value[key] = value + + if key is not None: + dict.__setitem__(self, key, value) + + if is_replace: + return + m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) + if not m: + return + + indent = m.group(1) + + if not isinstance(value, Whitespace): + m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) + if not m: + value.trivia.indent = indent + else: + value.trivia.indent = m.group(1) + indent + m.group(2) -class Table(Item, dict): +class Table(AbstractTable): """ A table literal. """ def __init__( self, - value, - trivia, - is_aot_element, - is_super_table=False, - name=None, - display_name=None, - ): # type: (tomlkit.container.Container, Trivia, bool, bool, Optional[str], Optional[str]) -> None - super(Table, self).__init__(trivia) + value: "container.Container", + trivia: Trivia, + is_aot_element: bool, + is_super_table: Optional[bool] = None, + name: Optional[str] = None, + display_name: Optional[str] = None, + ) -> None: + super().__init__(value, trivia) self.name = name self.display_name = display_name - self._value = value self._is_aot_element = is_aot_element self._is_super_table = is_super_table - for k, v in self._value.body: - if k is not None: - super(Table, self).__setitem__(k.key, v) - - @property - def value(self): # type: () -> tomlkit.container.Container - return self._value - @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 9 - def add(self, key, item=None): # type: (Union[Key, Item, str], Any) -> Item - if item is None: - if not isinstance(key, (Comment, Whitespace)): - raise ValueError( - "Non comment/whitespace items must have an associated key" - ) - - key, item = None, key - - return self.append(key, item) + def __copy__(self) -> "Table": + return type(self)( + self._value.copy(), + self._trivia.copy(), + self._is_aot_element, + self._is_super_table, + self.name, + self.display_name, + ) - def append(self, key, _item): # type: (Union[Key, str], Any) -> Table + def append(self, key, _item): """ Appends a (key, item) to the table. """ if not isinstance(_item, Item): - _item = item(_item) + _item = item(_item, _parent=self) self._value.append(key, _item) if isinstance(key, Key): - key = key.key + key = next(iter(key)).key + _item = self._value[key] if key is not None: - super(Table, self).__setitem__(key, _item) + dict.__setitem__(self, key, _item) - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) + m = re.match(r"(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: return self @@ -941,114 +1605,66 @@ def append(self, key, _item): # type: (Union[Key, str], Any) -> Table return self - def raw_append(self, key, _item): # type: (Union[Key, str], Any) -> Table + def raw_append(self, key: Union[Key, str], _item: Any) -> "Table": + """Similar to :meth:`append` but does not copy indentation.""" if not isinstance(_item, Item): _item = item(_item) self._value.append(key, _item) if isinstance(key, Key): - key = key.key - - if key is not None: - super(Table, self).__setitem__(key, _item) - - return self - - def remove(self, key): # type: (Union[Key, str]) -> Table - self._value.remove(key) - - if isinstance(key, Key): - key = key.key + key = next(iter(key)).key + _item = self._value[key] if key is not None: - super(Table, self).__delitem__(key) + dict.__setitem__(self, key, _item) return self - def is_aot_element(self): # type: () -> bool + def is_aot_element(self) -> bool: + """True if the table is the direct child of an AOT element.""" return self._is_aot_element - def is_super_table(self): # type: () -> bool - return self._is_super_table - - def as_string(self): # type: () -> str + def is_super_table(self) -> bool: + """A super table is the intermediate parent of a nested table as in [a.b.c]. + If true, it won't appear in the TOML representation.""" + if self._is_super_table is not None: + return self._is_super_table + # If the table has only one child and that child is a table, then it is a super table. + if len(self) != 1: + return False + only_child = next(iter(self.values())) + return isinstance(only_child, (Table, AoT)) + + def as_string(self) -> str: return self._value.as_string() # Helpers - def indent(self, indent): # type: (int) -> Table - super(Table, self).indent(indent) + def indent(self, indent: int) -> "Table": + """Indent the table with given number of spaces.""" + super().indent(indent) m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if not m: - indent = "" + indent_str = "" else: - indent = m.group(1) + indent_str = m.group(1) - for k, item in self._value.body: + for _, item in self._value.body: if not isinstance(item, Whitespace): - item.trivia.indent = indent + item.trivia.indent + item.trivia.indent = indent_str + item.trivia.indent return self - def keys(self): # type: () -> Generator[str] - for k in self._value.keys(): - yield k - - def values(self): # type: () -> Generator[Item] - for v in self._value.values(): - yield v - - def items(self): # type: () -> Generator[Item] - for k, v in self._value.items(): - yield k, v - - def update(self, other): # type: (Dict) -> None - for k, v in other.items(): - self[k] = v - - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._value.get(key, default) - - def __contains__(self, key): # type: (Union[Key, str]) -> bool - return key in self._value - - def __getitem__(self, key): # type: (Union[Key, str]) -> Item - return self._value[key] + def invalidate_display_name(self): + self.display_name = None - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - if not isinstance(value, Item): - value = item(value) - - self._value[key] = value - - if key is not None: - super(Table, self).__setitem__(key, value) - - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - return - - indent = m.group(1) + for child in self.values(): + if hasattr(child, "invalidate_display_name"): + child.invalidate_display_name() - if not isinstance(value, Whitespace): - m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) - if not m: - value.trivia.indent = indent - else: - value.trivia.indent = m.group(1) + indent + m.group(2) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - self.remove(key) - - def __repr__(self): - return super(Table, self).__repr__() - - def __str__(self): - return str(self.value) - - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return ( self._value, self._trivia, @@ -1059,37 +1675,28 @@ def _getstate(self, protocol=3): ) -class InlineTable(Item, dict): +class InlineTable(AbstractTable): """ An inline table literal. """ def __init__( - self, value, trivia, new=False - ): # type: (tomlkit.container.Container, Trivia, bool) -> None - super(InlineTable, self).__init__(trivia) + self, value: "container.Container", trivia: Trivia, new: bool = False + ) -> None: + super().__init__(value, trivia) - self._value = value self._new = new - for k, v in self._value.body: - if k is not None: - super(InlineTable, self).__setitem__(k.key, v) - @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 10 - @property - def value(self): # type: () -> Dict - return self._value - - def append(self, key, _item): # type: (Union[Key, str], Any) -> InlineTable + def append(self, key, _item): """ Appends a (key, item) to the table. """ if not isinstance(_item, Item): - _item = item(_item) + _item = item(_item, _parent=self) if not isinstance(_item, (Whitespace, Comment)): if not _item.trivia.indent and len(self._value) > 0 and not self._new: @@ -1103,22 +1710,11 @@ def append(self, key, _item): # type: (Union[Key, str], Any) -> InlineTable key = key.key if key is not None: - super(InlineTable, self).__setitem__(key, _item) + dict.__setitem__(self, key, _item) return self - def remove(self, key): # type: (Union[Key, str]) -> InlineTable - self._value.remove(key) - - if isinstance(key, Key): - key = key.key - - if key is not None: - super(InlineTable, self).__delitem__(key) - - return self - - def as_string(self): # type: () -> str + def as_string(self) -> str: buf = "{" for i, (k, v) in enumerate(self._value.body): if k is None: @@ -1132,13 +1728,14 @@ def as_string(self): # type: () -> str continue - buf += "{}{}{}{}{}{}".format( - v.trivia.indent, - k.as_string() + ("." if k.is_dotted() else ""), - k.sep, - v.as_string(), - v.trivia.comment, - v.trivia.trail.replace("\n", ""), + v_trivia_trail = v.trivia.trail.replace("\n", "") + buf += ( + f"{v.trivia.indent}" + f'{k.as_string() + ("." if k.is_dotted() else "")}' + f"{k.sep}" + f"{v.as_string()}" + f"{v.trivia.comment}" + f"{v_trivia_trail}" ) if i != len(self._value.body) - 1: @@ -1150,167 +1747,178 @@ def as_string(self): # type: () -> str return buf - def keys(self): # type: () -> Generator[str] - for k in self._value.keys(): - yield k - - def values(self): # type: () -> Generator[Item] - for v in self._value.values(): - yield v - - def items(self): # type: () -> Generator[Item] - for k, v in self._value.items(): - yield k, v - - def update(self, other): # type: (Dict) -> None - for k, v in other.items(): - self[k] = v - - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any - return self._value.get(key, default) - - def __contains__(self, key): # type: (Union[Key, str]) -> bool - return key in self._value - - def __getitem__(self, key): # type: (Union[Key, str]) -> Item - return self._value[key] - - def __setitem__(self, key, value): # type: (Union[Key, str], Any) -> None - if not isinstance(value, Item): - value = item(value) - - self._value[key] = value - - if key is not None: - super(InlineTable, self).__setitem__(key, value) - if value.trivia.comment: + def __setitem__(self, key: Union[Key, str], value: Any) -> None: + if hasattr(value, "trivia") and value.trivia.comment: value.trivia.comment = "" + super().__setitem__(key, value) - m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) - if not m: - return - - indent = m.group(1) - - if not isinstance(value, Whitespace): - m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) - if not m: - value.trivia.indent = indent - else: - value.trivia.indent = m.group(1) + indent + m.group(2) - - def __delitem__(self, key): # type: (Union[Key, str]) -> None - self.remove(key) + def __copy__(self) -> "InlineTable": + return type(self)(self._value.copy(), self._trivia.copy(), self._new) - def __repr__(self): - return super(InlineTable, self).__repr__() - - def _getstate(self, protocol=3): + def _getstate(self, protocol: int = 3) -> tuple: return (self._value, self._trivia) -class String(unicode, Item): +class String(str, Item): """ A string literal. """ def __new__(cls, t, value, original, trivia): - return super(String, cls).__new__(cls, value) + return super().__new__(cls, value) - def __init__( - self, t, _, original, trivia - ): # type: (StringType, str, original, Trivia) -> None - super(String, self).__init__(trivia) + def __init__(self, t: StringType, _: str, original: str, trivia: Trivia) -> None: + super().__init__(trivia) self._t = t self._original = original + def unwrap(self) -> str: + return str(self) + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 11 @property - def value(self): # type: () -> str + def value(self) -> str: return self - def as_string(self): # type: () -> str - return "{}{}{}".format(self._t.value, decode(self._original), self._t.value) + def as_string(self) -> str: + return f"{self._t.value}{decode(self._original)}{self._t.value}" - def __add__(self, other): - result = super(String, self).__add__(other) + def __add__(self: ItemT, other: str) -> ItemT: + if not isinstance(other, str): + return NotImplemented + result = super().__add__(other) + original = self._original + getattr(other, "_original", other) - return self._new(result) + return self._new(result, original) - def __sub__(self, other): - result = super(String, self).__sub__(other) + def _new(self, result: str, original: str) -> "String": + return String(self._t, result, original, self._trivia) - return self._new(result) + def _getstate(self, protocol=3): + return self._t, str(self), self._original, self._trivia - def _new(self, result): - return String(self._t, result, result, self._trivia) + @classmethod + def from_raw(cls, value: str, type_=StringType.SLB, escape=True) -> "String": + value = decode(value) - def _getstate(self, protocol=3): - return self._t, unicode(self), self._original, self._trivia + invalid = type_.invalid_sequences + if any(c in value for c in invalid): + raise InvalidStringError(value, invalid, type_.value) + escaped = type_.escaped_sequences + string_value = escape_string(value, escaped) if escape and escaped else value -class AoT(Item, list): + return cls(type_, decode(value), string_value, Trivia()) + + +class AoT(Item, _CustomList): """ An array of table literal """ def __init__( - self, body, name=None, parsed=False - ): # type: (List[Table], Optional[str], bool) -> None + self, body: List[Table], name: Optional[str] = None, parsed: bool = False + ) -> None: self.name = name - self._body = [] + self._body: List[Table] = [] self._parsed = parsed - super(AoT, self).__init__(Trivia(trail="")) + super().__init__(Trivia(trail="")) for table in body: self.append(table) + def unwrap(self) -> List[Dict[str, Any]]: + unwrapped = [] + for t in self._body: + if isinstance(t, Item): + unwrapped.append(t.unwrap()) + else: + unwrapped.append(t) + return unwrapped + @property - def body(self): # type: () -> List[Table] + def body(self) -> List[Table]: return self._body @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return 12 @property - def value(self): # type: () -> List[Dict[Any, Any]] + def value(self) -> List[Dict[Any, Any]]: return [v.value for v in self._body] - def append(self, table): # type: (Table) -> Table + def __len__(self) -> int: + return len(self._body) + + @overload + def __getitem__(self, key: slice) -> List[Table]: + ... + + @overload + def __getitem__(self, key: int) -> Table: + ... + + def __getitem__(self, key): + return self._body[key] + + def __setitem__(self, key: Union[slice, int], value: Any) -> None: + raise NotImplementedError + + def __delitem__(self, key: Union[slice, int]) -> None: + del self._body[key] + list.__delitem__(self, key) + + def insert(self, index: int, value: dict) -> None: + value = item(value, _parent=self) + if not isinstance(value, Table): + raise ValueError(f"Unsupported insert value type: {type(value)}") + length = len(self) + if index < 0: + index += length + if index < 0: + index = 0 + elif index >= length: + index = length m = re.match("(?s)^[^ ]*([ ]+).*$", self._trivia.indent) if m: indent = m.group(1) - m = re.match("(?s)^([^ ]*)(.*)$", table.trivia.indent) + m = re.match("(?s)^([^ ]*)(.*)$", value.trivia.indent) if not m: - table.trivia.indent = indent + value.trivia.indent = indent else: - table.trivia.indent = m.group(1) + indent + m.group(2) - - if not self._parsed and "\n" not in table.trivia.indent and self._body: - table.trivia.indent = "\n" + table.trivia.indent - - self._body.append(table) - - super(AoT, self).append(table) - - return table - - def as_string(self): # type: () -> str + value.trivia.indent = m.group(1) + indent + m.group(2) + prev_table = self._body[index - 1] if 0 < index and length else None + next_table = self._body[index + 1] if index < length - 1 else None + if not self._parsed: + if prev_table and "\n" not in value.trivia.indent: + value.trivia.indent = "\n" + value.trivia.indent + if next_table and "\n" not in next_table.trivia.indent: + next_table.trivia.indent = "\n" + next_table.trivia.indent + self._body.insert(index, value) + list.insert(self, index, value) + + def invalidate_display_name(self): + """Call ``invalidate_display_name`` on the contained tables""" + for child in self: + if hasattr(child, "invalidate_display_name"): + child.invalidate_display_name() + + def as_string(self) -> str: b = "" for table in self._body: b += table.as_string() return b - def __repr__(self): # type: () -> str - return "".format(self.value) + def __repr__(self) -> str: + return f"" def _getstate(self, protocol=3): return self._body, self.name, self._parsed @@ -1321,19 +1929,22 @@ class Null(Item): A null item. """ - def __init__(self): # type: () -> None + def __init__(self) -> None: pass + def unwrap(self) -> None: + return None + @property - def discriminant(self): # type: () -> int + def discriminant(self) -> int: return -1 @property - def value(self): # type: () -> None + def value(self) -> None: return None - def as_string(self): # type: () -> str + def as_string(self) -> str: return "" - def _getstate(self, protocol=3): - return tuple() + def _getstate(self, protocol=3) -> tuple: + return () diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/parser.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/parser.py index 499299542..c6393a575 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/parser.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/parser.py @@ -1,58 +1,55 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - +import datetime import re import string -from typing import Any -from typing import Generator from typing import List from typing import Optional from typing import Tuple +from typing import Type from typing import Union -from ._compat import chr -from ._compat import decode -from ._utils import RFC_3339_LOOSE -from ._utils import _escaped -from ._utils import parse_rfc3339 -from .container import Container -from .exceptions import EmptyKeyError -from .exceptions import EmptyTableNameError -from .exceptions import InternalParserError -from .exceptions import InvalidCharInStringError -from .exceptions import InvalidControlChar -from .exceptions import InvalidDateError -from .exceptions import InvalidDateTimeError -from .exceptions import InvalidNumberError -from .exceptions import InvalidTimeError -from .exceptions import InvalidUnicodeValueError -from .exceptions import ParseError -from .exceptions import UnexpectedCharError -from .exceptions import UnexpectedEofError -from .items import AoT -from .items import Array -from .items import Bool -from .items import BoolType -from .items import Comment -from .items import Date -from .items import DateTime -from .items import Float -from .items import InlineTable -from .items import Integer -from .items import Item -from .items import Key -from .items import KeyType -from .items import Null -from .items import String -from .items import StringType -from .items import Table -from .items import Time -from .items import Trivia -from .items import Whitespace -from .source import Source -from .toml_char import TOMLChar -from .toml_document import TOMLDocument +from tomlkit._compat import decode +from tomlkit._utils import RFC_3339_LOOSE +from tomlkit._utils import _escaped +from tomlkit._utils import parse_rfc3339 +from tomlkit.container import Container +from tomlkit.exceptions import EmptyKeyError +from tomlkit.exceptions import EmptyTableNameError +from tomlkit.exceptions import InternalParserError +from tomlkit.exceptions import InvalidCharInStringError +from tomlkit.exceptions import InvalidControlChar +from tomlkit.exceptions import InvalidDateError +from tomlkit.exceptions import InvalidDateTimeError +from tomlkit.exceptions import InvalidNumberError +from tomlkit.exceptions import InvalidTimeError +from tomlkit.exceptions import InvalidUnicodeValueError +from tomlkit.exceptions import ParseError +from tomlkit.exceptions import UnexpectedCharError +from tomlkit.exceptions import UnexpectedEofError +from tomlkit.items import AoT +from tomlkit.items import Array +from tomlkit.items import Bool +from tomlkit.items import BoolType +from tomlkit.items import Comment +from tomlkit.items import Date +from tomlkit.items import DateTime +from tomlkit.items import Float +from tomlkit.items import InlineTable +from tomlkit.items import Integer +from tomlkit.items import Item +from tomlkit.items import Key +from tomlkit.items import KeyType +from tomlkit.items import Null +from tomlkit.items import SingleKey +from tomlkit.items import String +from tomlkit.items import StringType +from tomlkit.items import Table +from tomlkit.items import Time +from tomlkit.items import Trivia +from tomlkit.items import Whitespace +from tomlkit.source import Source +from tomlkit.toml_char import TOMLChar +from tomlkit.toml_document import TOMLDocument CTRL_I = 0x09 # Tab @@ -67,11 +64,11 @@ class Parser: Parser for TOML documents. """ - def __init__(self, string): # type: (str) -> None + def __init__(self, string: str) -> None: # Input to parse self._src = Source(decode(string)) - self._aot_stack = [] + self._aot_stack: List[Key] = [] @property def _state(self): @@ -89,20 +86,20 @@ def _current(self): def _marker(self): return self._src.marker - def extract(self): # type: () -> str + def extract(self) -> str: """ Extracts the value between marker and index """ return self._src.extract() - def inc(self, exception=None): # type: (Optional[ParseError.__class__]) -> bool + def inc(self, exception: Optional[Type[ParseError]] = None) -> bool: """ Increments the parser if the end of the input has not been reached. Returns whether or not it was able to advance. """ return self._src.inc(exception=exception) - def inc_n(self, n, exception=None): # type: (int, Optional[ParseError]) -> bool + def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool: """ Increments the parser by n characters if the end of the input has not been reached. @@ -115,25 +112,25 @@ def consume(self, chars, min=0, max=-1): """ return self._src.consume(chars=chars, min=min, max=max) - def end(self): # type: () -> bool + def end(self) -> bool: """ Returns True if the parser has reached the end of the input. """ return self._src.end() - def mark(self): # type: () -> None + def mark(self) -> None: """ Sets the marker to the index's current position """ self._src.mark() - def parse_error(self, exception=ParseError, *args): + def parse_error(self, exception=ParseError, *args, **kwargs): """ Creates a generic "parse error" at the current position. """ - return self._src.parse_error(exception, *args) + return self._src.parse_error(exception, *args, **kwargs) - def parse(self): # type: () -> TOMLDocument + def parse(self) -> TOMLDocument: body = TOMLDocument(True) # Take all keyvals outside of tables/AoT's. @@ -148,11 +145,12 @@ def parse(self): # type: () -> TOMLDocument break key, value = item - if key is not None and key.is_dotted(): + if (key is not None and key.is_multi()) or not self._merge_ws(value, body): # We actually have a table - self._handle_dotted_key(body, key, value) - elif not self._merge_ws(value, body): - body.append(key, value) + try: + body.append(key, value) + except Exception as e: + raise self.parse_error(ParseError, str(e)) from e self.mark() @@ -161,15 +159,18 @@ def parse(self): # type: () -> TOMLDocument if isinstance(value, Table) and value.is_aot_element(): # This is just the first table in an AoT. Parse the rest of the array # along with it. - value = self._parse_aot(value, key.key) + value = self._parse_aot(value, key) - body.append(key, value) + try: + body.append(key, value) + except Exception as e: + raise self.parse_error(ParseError, str(e)) from e body.parsing(False) return body - def _merge_ws(self, item, container): # type: (Item, Container) -> bool + def _merge_ws(self, item: Item, container: Container) -> bool: """ Merges the given Item with the last one currently in the given Container if both are whitespace items. @@ -191,85 +192,20 @@ def _merge_ws(self, item, container): # type: (Item, Container) -> bool return True - def _is_child(self, parent, child): # type: (str, str) -> bool + def _is_child(self, parent: Key, child: Key) -> bool: """ Returns whether a key is strictly a child of another key. AoT siblings are not considered children of one another. """ - parent_parts = tuple(self._split_table_name(parent)) - child_parts = tuple(self._split_table_name(child)) + parent_parts = tuple(parent) + child_parts = tuple(child) if parent_parts == child_parts: return False return parent_parts == child_parts[: len(parent_parts)] - def _split_table_name(self, name): # type: (str) -> Generator[Key] - in_name = False - current = "" - t = KeyType.Bare - parts = 0 - for c in name: - c = TOMLChar(c) - - if c == ".": - if in_name: - current += c - continue - - if not current: - raise self.parse_error() - - yield Key(current.strip(), t=t, sep="", original=current) - - parts += 1 - - current = "" - t = KeyType.Bare - continue - elif c in {"'", '"'}: - if in_name: - if ( - t == KeyType.Literal - and c == '"' - or t == KeyType.Basic - and c == "'" - ): - current += c - continue - - if c != t.value: - raise self.parse_error() - - in_name = False - else: - if ( - current.strip() - and TOMLChar(current[-1]).is_spaces() - and not parts - ): - raise self.parse_error() - - in_name = True - t = KeyType.Literal if c == "'" else KeyType.Basic - - continue - elif in_name or c.is_bare_key_char(): - current += c - elif c.is_spaces(): - # A space is only valid at this point - # if it's in between parts. - # We store it for now and will check - # later if it's valid - current += c - continue - else: - raise self.parse_error() - - if current.strip(): - yield Key(current.strip(), t=t, sep="", original=current) - - def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] + def _parse_item(self) -> Optional[Tuple[Optional[Key], Item]]: """ Attempts to parse the next item and returns it, along with its key if the item is value-like. @@ -297,7 +233,7 @@ def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] # Found a table, delegate to the calling function. return else: - # Begining of a KV pair. + # Beginning of a KV pair. # Return to beginning of whitespace so it gets included # as indentation for the KV about to be parsed. state.restore = True @@ -305,7 +241,7 @@ def _parse_item(self): # type: () -> Optional[Tuple[Optional[Key], Item]] return self._parse_key_value(True) - def _parse_comment_trail(self): # type: () -> Tuple[str, str, str] + def _parse_comment_trail(self, parse_trail: bool = True) -> Tuple[str, str, str]: """ Returns (comment_ws, comment, trail) If there is no comment, comment_ws and comment will @@ -350,22 +286,23 @@ def _parse_comment_trail(self): # type: () -> Tuple[str, str, str] if self.end(): break - while self._current.is_spaces() and self.inc(): - pass + trail = "" + if parse_trail: + while self._current.is_spaces() and self.inc(): + pass - if self._current == "\r": - self.inc() + if self._current == "\r": + self.inc() - if self._current == "\n": - self.inc() + if self._current == "\n": + self.inc() - trail = "" - if self._idx != self._marker or self._current.is_ws(): - trail = self.extract() + if self._idx != self._marker or self._current.is_ws(): + trail = self.extract() return comment_ws, comment, trail - def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item) + def _parse_key_value(self, parse_comment: bool = False) -> Tuple[Key, Item]: # Leading indent self.mark() @@ -386,7 +323,8 @@ def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item) raise self.parse_error(UnexpectedCharError, "=") else: found_equals = True - pass + if not found_equals: + raise self.parse_error(UnexpectedCharError, self._current) if not key.sep: key.sep = self.extract() @@ -411,57 +349,53 @@ def _parse_key_value(self, parse_comment=False): # type: (bool) -> (Key, Item) return key, val - def _parse_key(self): # type: () -> Key + def _parse_key(self) -> Key: """ Parses a Key at the current position; WS before the key must be exhausted first at the callsite. """ + self.mark() + while self._current.is_spaces() and self.inc(): + # Skip any leading whitespace + pass if self._current in "\"'": return self._parse_quoted_key() else: return self._parse_bare_key() - def _parse_quoted_key(self): # type: () -> Key + def _parse_quoted_key(self) -> Key: """ Parses a key enclosed in either single or double quotes. """ + # Extract the leading whitespace + original = self.extract() quote_style = self._current - key_type = None - dotted = False - for t in KeyType: - if t.value == quote_style: - key_type = t - break + key_type = next((t for t in KeyType if t.value == quote_style), None) if key_type is None: raise RuntimeError("Should not have entered _parse_quoted_key()") - self.inc() + key_str = self._parse_string( + StringType.SLB if key_type == KeyType.Basic else StringType.SLL + ) + if key_str._t.is_multiline(): + raise self.parse_error(UnexpectedCharError, key_str._t.value) + original += key_str.as_string() self.mark() - - while self._current != quote_style and self.inc(): + while self._current.is_spaces() and self.inc(): pass - - key = self.extract() - + original += self.extract() + key = SingleKey(str(key_str), t=key_type, sep="", original=original) if self._current == ".": self.inc() - dotted = True - key += "." + self._parse_key().as_string() - key_type = KeyType.Bare - else: - self.inc() + key = key.concat(self._parse_key()) - return Key(key, key_type, "", dotted) + return key - def _parse_bare_key(self): # type: () -> Key + def _parse_bare_key(self) -> Key: """ Parses a bare key. """ - key_type = None - dotted = False - - self.mark() while ( self._current.is_bare_key_char() or self._current.is_spaces() ) and self.inc(): @@ -471,85 +405,21 @@ def _parse_bare_key(self): # type: () -> Key key = original.strip() if not key: # Empty key - raise self.parse_error(ParseError, "Empty key found") + raise self.parse_error(EmptyKeyError) if " " in key: # Bare key with spaces in it - raise self.parse_error(ParseError, 'Invalid key "{}"'.format(key)) + raise self.parse_error(ParseError, f'Invalid key "{key}"') + + key = SingleKey(key, KeyType.Bare, "", original) if self._current == ".": self.inc() - dotted = True - original += "." + self._parse_key().as_string() - key = original.strip() - key_type = KeyType.Bare - - return Key(key, key_type, "", dotted, original=original) - - def _handle_dotted_key( - self, container, key, value - ): # type: (Union[Container, Table], Key, Any) -> None - names = tuple(self._split_table_name(key.as_string())) - name = names[0] - name._dotted = True - if name in container: - if not isinstance(value, Table): - table = Table(Container(True), Trivia(), False, is_super_table=True) - _table = table - for i, _name in enumerate(names[1:]): - if i == len(names) - 2: - _name.sep = key.sep - - _table.append(_name, value) - else: - _name._dotted = True - _table.append( - _name, - Table( - Container(True), - Trivia(), - False, - is_super_table=i < len(names) - 2, - ), - ) - - _table = _table[_name] + key = key.concat(self._parse_key()) - value = table + return key - container.append(name, value) - - return - else: - table = Table(Container(True), Trivia(), False, is_super_table=True) - if isinstance(container, Table): - container.raw_append(name, table) - else: - container.append(name, table) - - for i, _name in enumerate(names[1:]): - if i == len(names) - 2: - _name.sep = key.sep - - table.append(_name, value) - else: - _name._dotted = True - if _name in table.value: - table = table.value[_name] - else: - table.append( - _name, - Table( - Container(True), - Trivia(), - False, - is_super_table=i < len(names) - 2, - ), - ) - - table = table[_name] - - def _parse_value(self): # type: () -> Item + def _parse_value(self) -> Item: """ Attempts to parse a value at the current position. """ @@ -601,6 +471,7 @@ def _parse_value(self): # type: () -> Item # datetime try: dt = parse_rfc3339(raw) + assert isinstance(dt, datetime.datetime) return DateTime( dt.year, dt.month, @@ -619,6 +490,7 @@ def _parse_value(self): # type: () -> Item if m.group(1): try: dt = parse_rfc3339(raw) + assert isinstance(dt, datetime.date) date = Date(dt.year, dt.month, dt.day, trivia, raw) self.mark() while self._current not in "\t\n\r#,]}" and self.inc(): @@ -630,6 +502,7 @@ def _parse_value(self): # type: () -> Item return date dt = parse_rfc3339(raw + time_raw) + assert isinstance(dt, datetime.datetime) return DateTime( dt.year, dt.month, @@ -648,6 +521,7 @@ def _parse_value(self): # type: () -> Item if m.group(5): try: t = parse_rfc3339(raw) + assert isinstance(t, datetime.time) return Time( t.hour, t.minute, @@ -674,7 +548,7 @@ def _parse_true(self): def _parse_false(self): return self._parse_bool(BoolType.FALSE) - def _parse_bool(self, style): # type: (BoolType) -> Bool + def _parse_bool(self, style: BoolType) -> Bool: with self._state: style = BoolType(style) @@ -685,25 +559,25 @@ def _parse_bool(self, style): # type: (BoolType) -> Bool return Bool(style, Trivia()) - def _parse_array(self): # type: () -> Array + def _parse_array(self) -> Array: # Consume opening bracket, EOF here is an issue (middle of array) self.inc(exception=UnexpectedEofError) - elems = [] # type: List[Item] + elems: List[Item] = [] prev_value = None while True: # consume whitespace mark = self._idx - self.consume(TOMLChar.SPACES) - newline = self.consume(TOMLChar.NL) + self.consume(TOMLChar.SPACES + TOMLChar.NL) indent = self._src[mark : self._idx] + newline = set(TOMLChar.NL) & set(indent) if newline: elems.append(Whitespace(indent)) continue # consume comment if self._current == "#": - cws, comment, trail = self._parse_comment_trail() + cws, comment, trail = self._parse_comment_trail(parse_trail=False) elems.append(Comment(Trivia(indent, cws, comment, trail))) continue @@ -743,7 +617,7 @@ def _parse_array(self): # type: () -> Array else: return res - def _parse_inline_table(self): # type: () -> InlineTable + def _parse_inline_table(self) -> InlineTable: # consume opening bracket, EOF here is an issue (middle of array) self.inc(exception=UnexpectedEofError) @@ -779,10 +653,7 @@ def _parse_inline_table(self): # type: () -> InlineTable raise self.parse_error(UnexpectedCharError, self._current) key, val = self._parse_key_value(False) - if key.is_dotted(): - self._handle_dotted_key(elems, key, val) - else: - elems.add(key, val) + elems.add(key, val) # consume trailing whitespace mark = self._idx @@ -799,22 +670,23 @@ def _parse_inline_table(self): # type: () -> InlineTable return InlineTable(elems, Trivia()) - def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item] + def _parse_number(self, raw: str, trivia: Trivia) -> Optional[Item]: # Leading zeros are not allowed sign = "" if raw.startswith(("+", "-")): sign = raw[0] raw = raw[1:] - if ( - len(raw) > 1 - and raw.startswith("0") + if len(raw) > 1 and ( + raw.startswith("0") and not raw.startswith(("0.", "0o", "0x", "0b", "0e")) + or sign + and raw.startswith(".") ): - return + return None if raw.startswith(("0o", "0x", "0b")) and sign: - return + return None digits = "[0-9]" base = 10 @@ -829,13 +701,17 @@ def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item] base = 16 # Underscores should be surrounded by digits - clean = re.sub("(?i)(?<={})_(?={})".format(digits, digits), "", raw) + clean = re.sub(f"(?i)(?<={digits})_(?={digits})", "", raw).lower() if "_" in clean: - return + return None - if clean.endswith("."): - return + if ( + clean.endswith(".") + or not clean.startswith("0x") + and clean.split("e", 1)[0].endswith(".") + ): + return None try: return Integer(int(sign + clean, base), trivia, sign + raw) @@ -843,13 +719,13 @@ def _parse_number(self, raw, trivia): # type: (str, Trivia) -> Optional[Item] try: return Float(float(sign + clean), trivia, sign + raw) except ValueError: - return + return None - def _parse_literal_string(self): # type: () -> String + def _parse_literal_string(self) -> String: with self._state: return self._parse_string(StringType.SLL) - def _parse_basic_string(self): # type: () -> String + def _parse_basic_string(self) -> String: with self._state: return self._parse_string(StringType.SLB) @@ -898,12 +774,12 @@ def _parse_escaped_char(self, multiline): raise self.parse_error(InvalidCharInStringError, self._current) - def _parse_string(self, delim): # type: (StringType) -> String + def _parse_string(self, delim: StringType) -> String: # only keep parsing for string if the current character matches the delim if self._current != delim.unit: raise self.parse_error( InternalParserError, - "Invalid character for string type {}".format(delim), + f"Invalid character for string type {delim}", ) # consume the opening/first delim, EOF here is an issue @@ -937,9 +813,7 @@ def _parse_string(self, delim): # type: (StringType) -> String delim.is_singleline() and not escaped and (code == CHR_DEL or code <= CTRL_CHAR_LIMIT and code != CTRL_I) - ): - raise self.parse_error(InvalidControlChar, code, "strings") - elif ( + ) or ( delim.is_multiline() and not escaped and ( @@ -1006,8 +880,8 @@ def _parse_string(self, delim): # type: (StringType) -> String self.inc(exception=UnexpectedEofError) def _parse_table( - self, parent_name=None, parent=None - ): # type: (Optional[str], Optional[Table]) -> Tuple[Key, Union[Table, AoT]] + self, parent_name: Optional[Key] = None, parent: Optional[Table] = None + ) -> Tuple[Key, Union[Table, AoT]]: """ Parses a table element. """ @@ -1028,60 +902,30 @@ def _parse_table( raise self.parse_error(UnexpectedEofError) is_aot = True - - # Consume any whitespace - self.mark() - while self._current.is_spaces() and self.inc(): - pass - - ws_prefix = self.extract() - - # Key - if self._current in [StringType.SLL.value, StringType.SLB.value]: - delimiter = ( - StringType.SLL - if self._current == StringType.SLL.value - else StringType.SLB - ) - name = self._parse_string(delimiter) - name = "{delimiter}{name}{delimiter}".format( - delimiter=delimiter.value, name=name - ) - - self.mark() - while self._current != "]" and self.inc(): - if self.end(): - raise self.parse_error(UnexpectedEofError) - - pass - - ws_suffix = self.extract() - name += ws_suffix - else: - self.mark() - while self._current != "]" and self.inc(): - if self.end(): - raise self.parse_error(UnexpectedEofError) - - pass - - name = self.extract() - - name = ws_prefix + name - - if not name.strip(): + try: + key = self._parse_key() + except EmptyKeyError: + raise self.parse_error(EmptyTableNameError) from None + if self.end(): + raise self.parse_error(UnexpectedEofError) + elif self._current != "]": + raise self.parse_error(UnexpectedCharError, self._current) + elif not key.key.strip(): raise self.parse_error(EmptyTableNameError) - key = Key(name, sep="") - name_parts = tuple(self._split_table_name(name)) + key.sep = "" + full_key = key + name_parts = tuple(key) if any(" " in part.key.strip() and part.is_bare() for part in name_parts): - raise self.parse_error(ParseError, 'Invalid table name "{}"'.format(name)) + raise self.parse_error( + ParseError, f'Invalid table name "{full_key.as_string()}"' + ) missing_table = False if parent_name: - parent_name_parts = tuple(self._split_table_name(parent_name)) + parent_name_parts = tuple(parent_name) else: - parent_name_parts = tuple() + parent_name_parts = () if len(name_parts) > len(parent_name_parts) + 1: missing_table = True @@ -1102,8 +946,9 @@ def _parse_table( values, Trivia(indent, cws, comment, trail), is_aot, - name=name, - display_name=name, + name=name_parts[0].key if name_parts else key.key, + display_name=full_key.as_string(), + is_super_table=False, ) if len(name_parts) > 1: @@ -1116,34 +961,36 @@ def _parse_table( table = Table( Container(True), Trivia(indent, cws, comment, trail), - is_aot and name_parts[0].key in self._aot_stack, + is_aot and name_parts[0] in self._aot_stack, is_super_table=True, name=name_parts[0].key, ) - result = table - key = name_parts[0] - - for i, _name in enumerate(name_parts[1:]): - if _name in table: - child = table[_name] - else: - child = Table( - Container(True), - Trivia(indent, cws, comment, trail), - is_aot and i == len(name_parts[1:]) - 1, - is_super_table=i < len(name_parts[1:]) - 1, - name=_name.key, - display_name=name if i == len(name_parts[1:]) - 1 else None, - ) + result = table + key = name_parts[0] + + for i, _name in enumerate(name_parts[1:]): + child = table.get( + _name, + Table( + Container(True), + Trivia(indent, cws, comment, trail), + is_aot and i == len(name_parts) - 2, + is_super_table=i < len(name_parts) - 2, + name=_name.key, + display_name=full_key.as_string() + if i == len(name_parts) - 2 + else None, + ), + ) - if is_aot and i == len(name_parts[1:]) - 1: - table.append(_name, AoT([child], name=table.name, parsed=True)) - else: - table.append(_name, child) + if is_aot and i == len(name_parts) - 2: + table.raw_append(_name, AoT([child], name=table.name, parsed=True)) + else: + table.raw_append(_name, child) - table = child - values = table.value + table = child + values = table.value else: if name_parts: key = name_parts[0] @@ -1153,27 +1000,24 @@ def _parse_table( if item: _key, item = item if not self._merge_ws(item, values): - if _key is not None and _key.is_dotted(): - self._handle_dotted_key(table, _key, item) - else: - table.raw_append(_key, item) + table.raw_append(_key, item) else: if self._current == "[": - is_aot_next, name_next = self._peek_table() + _, key_next = self._peek_table() - if self._is_child(name, name_next): - key_next, table_next = self._parse_table(name, table) + if self._is_child(full_key, key_next): + key_next, table_next = self._parse_table(full_key, table) table.raw_append(key_next, table_next) # Picking up any sibling while not self.end(): - _, name_next = self._peek_table() + _, key_next = self._peek_table() - if not self._is_child(name, name_next): + if not self._is_child(full_key, key_next): break - key_next, table_next = self._parse_table(name, table) + key_next, table_next = self._parse_table(full_key, table) table.raw_append(key_next, table_next) @@ -1187,12 +1031,12 @@ def _parse_table( if isinstance(result, Null): result = table - if is_aot and (not self._aot_stack or name != self._aot_stack[-1]): - result = self._parse_aot(result, name) + if is_aot and (not self._aot_stack or full_key != self._aot_stack[-1]): + result = self._parse_aot(result, full_key) return key, result - def _peek_table(self): # type: () -> Tuple[bool, str] + def _peek_table(self) -> Tuple[bool, Key]: """ Peeks ahead non-intrusively by cloning then restoring the initial state of the parser. @@ -1214,15 +1058,12 @@ def _peek_table(self): # type: () -> Tuple[bool, str] if self._current == "[": self.inc() is_aot = True + try: + return is_aot, self._parse_key() + except EmptyKeyError: + raise self.parse_error(EmptyTableNameError) from None - self.mark() - - while self._current != "]" and self.inc(): - table_name = self.extract() - - return is_aot, table_name - - def _parse_aot(self, first, name_first): # type: (Table, str) -> AoT + def _parse_aot(self, first: Table, name_first: Key) -> AoT: """ Parses all siblings of the provided table first and bundles them into an AoT. @@ -1241,7 +1082,7 @@ def _parse_aot(self, first, name_first): # type: (Table, str) -> AoT return AoT(payload, parsed=True) - def _peek(self, n): # type: (int) -> str + def _peek(self, n: int) -> str: """ Peeks ahead n characters. @@ -1251,7 +1092,7 @@ def _peek(self, n): # type: (int) -> str with self._state(restore=True): buf = "" for _ in range(n): - if self._current not in " \t\n\r#,]}": + if self._current not in " \t\n\r#,]}" + self._src.EOF: buf += self._current self.inc() continue @@ -1259,9 +1100,7 @@ def _peek(self, n): # type: (int) -> str break return buf - def _peek_unicode( - self, is_long - ): # type: (bool) -> Tuple[Optional[str], Optional[str]] + def _peek_unicode(self, is_long: bool) -> Tuple[Optional[str], Optional[str]]: """ Peeks ahead non-intrusively by cloning then restoring the initial state of the parser. diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/py.typed b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/source.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/source.py index 6a6a23910..d1a53cdd5 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/source.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/source.py @@ -1,39 +1,28 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import itertools - from copy import copy from typing import Any from typing import Optional from typing import Tuple from typing import Type -from ._compat import PY2 -from ._compat import unicode -from .exceptions import ParseError -from .exceptions import UnexpectedCharError -from .exceptions import UnexpectedEofError -from .toml_char import TOMLChar +from tomlkit.exceptions import ParseError +from tomlkit.exceptions import UnexpectedCharError +from tomlkit.toml_char import TOMLChar class _State: def __init__( - self, source, save_marker=False, restore=False - ): # type: (_Source, Optional[bool], Optional[bool]) -> None + self, + source: "Source", + save_marker: Optional[bool] = False, + restore: Optional[bool] = False, + ) -> None: self._source = source self._save_marker = save_marker self.restore = restore - def __enter__(self): # type: () -> None + def __enter__(self) -> "_State": # Entering this context manager - save the state - if PY2: - # Python 2.7 does not allow to directly copy - # an iterator, so we have to make tees of the original - # chars iterator. - self._source._chars, self._chars = itertools.tee(self._source._chars) - else: - self._chars = copy(self._source._chars) + self._chars = copy(self._source._chars) self._idx = self._source._idx self._current = self._source._current self._marker = self._source._marker @@ -55,14 +44,14 @@ class _StateHandler: State preserver for the Parser. """ - def __init__(self, source): # type: (Source) -> None + def __init__(self, source: "Source") -> None: self._source = source self._states = [] def __call__(self, *args, **kwargs): return _State(self._source, *args, **kwargs) - def __enter__(self): # type: () -> None + def __enter__(self) -> None: state = self() self._states.append(state) return state.__enter__() @@ -72,11 +61,11 @@ def __exit__(self, exception_type, exception_val, trace): return state.__exit__(exception_type, exception_val, trace) -class Source(unicode): +class Source(str): EOF = TOMLChar("\0") - def __init__(self, _): # type: (unicode) -> None - super(Source, self).__init__() + def __init__(self, _: str) -> None: + super().__init__() # Collection of TOMLChars self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)]) @@ -97,28 +86,28 @@ def reset(self): self.mark() @property - def state(self): # type: () -> _StateHandler + def state(self) -> _StateHandler: return self._state @property - def idx(self): # type: () -> int + def idx(self) -> int: return self._idx @property - def current(self): # type: () -> TOMLChar + def current(self) -> TOMLChar: return self._current @property - def marker(self): # type: () -> int + def marker(self) -> int: return self._marker - def extract(self): # type: () -> unicode + def extract(self) -> str: """ Extracts the value between marker and index """ return self[self._marker : self._idx] - def inc(self, exception=None): # type: (Optional[Type[ParseError]]) -> bool + def inc(self, exception: Optional[Type[ParseError]] = None) -> bool: """ Increments the parser if the end of the input has not been reached. Returns whether or not it was able to advance. @@ -135,16 +124,12 @@ def inc(self, exception=None): # type: (Optional[Type[ParseError]]) -> bool return False - def inc_n(self, n, exception=None): # type: (int, Exception) -> bool + def inc_n(self, n: int, exception: Optional[Type[ParseError]] = None) -> bool: """ Increments the parser by n characters if the end of the input has not been reached. """ - for _ in range(n): - if not self.inc(exception=exception): - return False - - return True + return all(self.inc(exception=exception) for _ in range(n)) def consume(self, chars, min=0, max=-1): """ @@ -158,31 +143,34 @@ def consume(self, chars, min=0, max=-1): # failed to consume minimum number of characters if min > 0: - self.parse_error(UnexpectedCharError) + raise self.parse_error(UnexpectedCharError, self.current) - def end(self): # type: () -> bool + def end(self) -> bool: """ Returns True if the parser has reached the end of the input. """ return self._current is self.EOF - def mark(self): # type: () -> None + def mark(self) -> None: """ Sets the marker to the index's current position """ self._marker = self._idx def parse_error( - self, exception=ParseError, *args - ): # type: (Type[ParseError], Any) -> ParseError + self, + exception: Type[ParseError] = ParseError, + *args: Any, + **kwargs: Any, + ) -> ParseError: """ Creates a generic "parse error" at the current position. """ line, col = self._to_linecol() - return exception(line, col, *args) + return exception(line, col, *args, **kwargs) - def _to_linecol(self): # type: () -> Tuple[int, int] + def _to_linecol(self) -> Tuple[int, int]: cur = 0 for i, line in enumerate(self.splitlines()): if cur + len(line) + 1 > self.idx: diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_char.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_char.py index 079b16ccd..b4bb4110c 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_char.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_char.py @@ -1,18 +1,9 @@ import string -from ._compat import PY2 -from ._compat import unicode - -if PY2: - from functools32 import lru_cache -else: - from functools import lru_cache - - -class TOMLChar(unicode): +class TOMLChar(str): def __init__(self, c): - super(TOMLChar, self).__init__() + super().__init__() if len(self) > 1: raise ValueError("A TOML character must be of length 1") @@ -24,43 +15,37 @@ def __init__(self, c): NL = "\n\r" WS = SPACES + NL - @lru_cache(maxsize=None) - def is_bare_key_char(self): # type: () -> bool + def is_bare_key_char(self) -> bool: """ Whether the character is a valid bare key name or not. """ return self in self.BARE - @lru_cache(maxsize=None) - def is_kv_sep(self): # type: () -> bool + def is_kv_sep(self) -> bool: """ - Whether the character is a valid key/value separator ot not. + Whether the character is a valid key/value separator or not. """ return self in self.KV - @lru_cache(maxsize=None) - def is_int_float_char(self): # type: () -> bool + def is_int_float_char(self) -> bool: """ Whether the character if a valid integer or float value character or not. """ return self in self.NUMBER - @lru_cache(maxsize=None) - def is_ws(self): # type: () -> bool + def is_ws(self) -> bool: """ Whether the character is a whitespace character or not. """ return self in self.WS - @lru_cache(maxsize=None) - def is_nl(self): # type: () -> bool + def is_nl(self) -> bool: """ Whether the character is a new line character or not. """ return self in self.NL - @lru_cache(maxsize=None) - def is_spaces(self): # type: () -> bool + def is_spaces(self) -> bool: """ Whether the character is a space or not """ diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_document.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_document.py index b485e3029..71fac2e10 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_document.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_document.py @@ -1,4 +1,4 @@ -from .container import Container +from tomlkit.container import Container class TOMLDocument(Container): diff --git a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_file.py b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_file.py index 3b416664d..745913080 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_file.py +++ b/conda_lock/_vendor/poetry/core/_vendor/tomlkit/toml_file.py @@ -1,24 +1,58 @@ -import io +import os +import re -from typing import Any -from typing import Dict +from typing import TYPE_CHECKING -from .api import loads -from .toml_document import TOMLDocument +from tomlkit.api import loads +from tomlkit.toml_document import TOMLDocument -class TOMLFile(object): +if TYPE_CHECKING: + from _typeshed import StrPath as _StrPath +else: + from typing import Union + + _StrPath = Union[str, os.PathLike] + + +class TOMLFile: """ Represents a TOML file. + + :param path: path to the TOML file """ - def __init__(self, path): # type: (str) -> None + def __init__(self, path: _StrPath) -> None: self._path = path + self._linesep = os.linesep + + def read(self) -> TOMLDocument: + """Read the file content as a :class:`tomlkit.toml_document.TOMLDocument`.""" + with open(self._path, encoding="utf-8", newline="") as f: + content = f.read() + + # check if consistent line endings + num_newline = content.count("\n") + if num_newline > 0: + num_win_eol = content.count("\r\n") + if num_win_eol == num_newline: + self._linesep = "\r\n" + elif num_win_eol == 0: + self._linesep = "\n" + else: + self._linesep = "mixed" + + return loads(content) + + def write(self, data: TOMLDocument) -> None: + """Write the TOMLDocument to the file.""" + content = data.as_string() - def read(self): # type: () -> TOMLDocument - with io.open(self._path, encoding="utf-8") as f: - return loads(f.read()) + # apply linesep + if self._linesep == "\n": + content = content.replace("\r\n", "\n") + elif self._linesep == "\r\n": + content = re.sub(r"(? None - with io.open(self._path, "w", encoding="utf-8") as f: - f.write(data.as_string()) + with open(self._path, "w", encoding="utf-8", newline="") as f: + f.write(content) diff --git a/conda_lock/_vendor/poetry/core/_vendor/typing_extensions.LICENSE b/conda_lock/_vendor/poetry/core/_vendor/typing_extensions.LICENSE new file mode 100644 index 000000000..1df6b3b8d --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/typing_extensions.LICENSE @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/conda_lock/_vendor/poetry/core/_vendor/typing_extensions.py b/conda_lock/_vendor/poetry/core/_vendor/typing_extensions.py new file mode 100644 index 000000000..ef42417c2 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/_vendor/typing_extensions.py @@ -0,0 +1,2209 @@ +import abc +import collections +import collections.abc +import functools +import operator +import sys +import types as _types +import typing + + +__all__ = [ + # Super-special typing primitives. + 'Any', + 'ClassVar', + 'Concatenate', + 'Final', + 'LiteralString', + 'ParamSpec', + 'ParamSpecArgs', + 'ParamSpecKwargs', + 'Self', + 'Type', + 'TypeVar', + 'TypeVarTuple', + 'Unpack', + + # ABCs (from collections.abc). + 'Awaitable', + 'AsyncIterator', + 'AsyncIterable', + 'Coroutine', + 'AsyncGenerator', + 'AsyncContextManager', + 'ChainMap', + + # Concrete collection types. + 'ContextManager', + 'Counter', + 'Deque', + 'DefaultDict', + 'NamedTuple', + 'OrderedDict', + 'TypedDict', + + # Structural checks, a.k.a. protocols. + 'SupportsIndex', + + # One-off things. + 'Annotated', + 'assert_never', + 'assert_type', + 'clear_overloads', + 'dataclass_transform', + 'get_overloads', + 'final', + 'get_args', + 'get_origin', + 'get_type_hints', + 'IntVar', + 'is_typeddict', + 'Literal', + 'NewType', + 'overload', + 'override', + 'Protocol', + 'reveal_type', + 'runtime', + 'runtime_checkable', + 'Text', + 'TypeAlias', + 'TypeGuard', + 'TYPE_CHECKING', + 'Never', + 'NoReturn', + 'Required', + 'NotRequired', +] + +# for backward compatibility +PEP_560 = True +GenericMeta = type + +# The functions below are modified copies of typing internal helpers. +# They are needed by _ProtocolMeta and they provide support for PEP 646. + +_marker = object() + + +def _check_generic(cls, parameters, elen=_marker): + """Check correct count for parameters of a generic cls (internal helper). + This gives a nice error message in case of count mismatch. + """ + if not elen: + raise TypeError(f"{cls} is not a generic class") + if elen is _marker: + if not hasattr(cls, "__parameters__") or not cls.__parameters__: + raise TypeError(f"{cls} is not a generic class") + elen = len(cls.__parameters__) + alen = len(parameters) + if alen != elen: + if hasattr(cls, "__parameters__"): + parameters = [p for p in cls.__parameters__ if not _is_unpack(p)] + num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters) + if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples): + return + raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};" + f" actual {alen}, expected {elen}") + + +if sys.version_info >= (3, 10): + def _should_collect_from_parameters(t): + return isinstance( + t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType) + ) +elif sys.version_info >= (3, 9): + def _should_collect_from_parameters(t): + return isinstance(t, (typing._GenericAlias, _types.GenericAlias)) +else: + def _should_collect_from_parameters(t): + return isinstance(t, typing._GenericAlias) and not t._special + + +def _collect_type_vars(types, typevar_types=None): + """Collect all type variable contained in types in order of + first appearance (lexicographic order). For example:: + + _collect_type_vars((T, List[S, T])) == (T, S) + """ + if typevar_types is None: + typevar_types = typing.TypeVar + tvars = [] + for t in types: + if ( + isinstance(t, typevar_types) and + t not in tvars and + not _is_unpack(t) + ): + tvars.append(t) + if _should_collect_from_parameters(t): + tvars.extend([t for t in t.__parameters__ if t not in tvars]) + return tuple(tvars) + + +NoReturn = typing.NoReturn + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = typing.TypeVar('T') # Any type. +KT = typing.TypeVar('KT') # Key type. +VT = typing.TypeVar('VT') # Value type. +T_co = typing.TypeVar('T_co', covariant=True) # Any type covariant containers. +T_contra = typing.TypeVar('T_contra', contravariant=True) # Ditto contravariant. + + +if sys.version_info >= (3, 11): + from typing import Any +else: + + class _AnyMeta(type): + def __instancecheck__(self, obj): + if self is Any: + raise TypeError("typing_extensions.Any cannot be used with isinstance()") + return super().__instancecheck__(obj) + + def __repr__(self): + if self is Any: + return "typing_extensions.Any" + return super().__repr__() + + class Any(metaclass=_AnyMeta): + """Special type indicating an unconstrained type. + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + checks. + """ + def __new__(cls, *args, **kwargs): + if cls is Any: + raise TypeError("Any cannot be instantiated") + return super().__new__(cls, *args, **kwargs) + + +ClassVar = typing.ClassVar + +# On older versions of typing there is an internal class named "Final". +# 3.8+ +if hasattr(typing, 'Final') and sys.version_info[:2] >= (3, 7): + Final = typing.Final +# 3.7 +else: + class _FinalForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + Final = _FinalForm('Final', + doc="""A special typing construct to indicate that a name + cannot be re-assigned or overridden in a subclass. + For example: + + MAX_SIZE: Final = 9000 + MAX_SIZE += 1 # Error reported by type checker + + class Connection: + TIMEOUT: Final[int] = 10 + class FastConnector(Connection): + TIMEOUT = 1 # Error reported by type checker + + There is no runtime checking of these properties.""") + +if sys.version_info >= (3, 11): + final = typing.final +else: + # @final exists in 3.8+, but we backport it for all versions + # before 3.11 to keep support for the __final__ attribute. + # See https://bugs.python.org/issue46342 + def final(f): + """This decorator can be used to indicate to type checkers that + the decorated method cannot be overridden, and decorated class + cannot be subclassed. For example: + + class Base: + @final + def done(self) -> None: + ... + class Sub(Base): + def done(self) -> None: # Error reported by type checker + ... + @final + class Leaf: + ... + class Other(Leaf): # Error reported by type checker + ... + + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. + """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass + return f + + +def IntVar(name): + return typing.TypeVar(name) + + +# 3.8+: +if hasattr(typing, 'Literal'): + Literal = typing.Literal +# 3.7: +else: + class _LiteralForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return typing._GenericAlias(self, parameters) + + Literal = _LiteralForm('Literal', + doc="""A type that can be used to indicate to type checkers + that the corresponding value has a value literally equivalent + to the provided parameter. For example: + + var: Literal[4] = 4 + + The type checker understands that 'var' is literally equal to + the value 4 and no other value. + + Literal[...] cannot be subclassed. There is no runtime + checking verifying that the parameter is actually a value + instead of a type.""") + + +_overload_dummy = typing._overload_dummy # noqa + + +if hasattr(typing, "get_overloads"): # 3.11+ + overload = typing.overload + get_overloads = typing.get_overloads + clear_overloads = typing.clear_overloads +else: + # {module: {qualname: {firstlineno: func}}} + _overload_registry = collections.defaultdict( + functools.partial(collections.defaultdict, dict) + ) + + def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + + The overloads for a function can be retrieved at runtime using the + get_overloads() function. + """ + # classmethod and staticmethod + f = getattr(func, "__func__", func) + try: + _overload_registry[f.__module__][f.__qualname__][ + f.__code__.co_firstlineno + ] = func + except AttributeError: + # Not a normal function; ignore. + pass + return _overload_dummy + + def get_overloads(func): + """Return all defined overloads for *func* as a sequence.""" + # classmethod and staticmethod + f = getattr(func, "__func__", func) + if f.__module__ not in _overload_registry: + return [] + mod_dict = _overload_registry[f.__module__] + if f.__qualname__ not in mod_dict: + return [] + return list(mod_dict[f.__qualname__].values()) + + def clear_overloads(): + """Clear all overloads in the registry.""" + _overload_registry.clear() + + +# This is not a real generic class. Don't use outside annotations. +Type = typing.Type + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + + +Awaitable = typing.Awaitable +Coroutine = typing.Coroutine +AsyncIterable = typing.AsyncIterable +AsyncIterator = typing.AsyncIterator +Deque = typing.Deque +ContextManager = typing.ContextManager +AsyncContextManager = typing.AsyncContextManager +DefaultDict = typing.DefaultDict + +# 3.7.2+ +if hasattr(typing, 'OrderedDict'): + OrderedDict = typing.OrderedDict +# 3.7.0-3.7.2 +else: + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) + +Counter = typing.Counter +ChainMap = typing.ChainMap +AsyncGenerator = typing.AsyncGenerator +NewType = typing.NewType +Text = typing.Text +TYPE_CHECKING = typing.TYPE_CHECKING + + +_PROTO_WHITELIST = ['Callable', 'Awaitable', + 'Iterable', 'Iterator', 'AsyncIterable', 'AsyncIterator', + 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', + 'ContextManager', 'AsyncContextManager'] + + +def _get_protocol_attrs(cls): + attrs = set() + for base in cls.__mro__[:-1]: # without object + if base.__name__ in ('Protocol', 'Generic'): + continue + annotations = getattr(base, '__annotations__', {}) + for attr in list(base.__dict__.keys()) + list(annotations.keys()): + if (not attr.startswith('_abc_') and attr not in ( + '__abstractmethods__', '__annotations__', '__weakref__', + '_is_protocol', '_is_runtime_protocol', '__dict__', + '__args__', '__slots__', + '__next_in_mro__', '__parameters__', '__origin__', + '__orig_bases__', '__extra__', '__tree_hash__', + '__doc__', '__subclasshook__', '__init__', '__new__', + '__module__', '_MutableMapping__marker', '_gorg')): + attrs.add(attr) + return attrs + + +def _is_callable_members_only(cls): + return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls)) + + +def _maybe_adjust_parameters(cls): + """Helper function used in Protocol.__init_subclass__ and _TypedDictMeta.__new__. + + The contents of this function are very similar + to logic found in typing.Generic.__init_subclass__ + on the CPython main branch. + """ + tvars = [] + if '__orig_bases__' in cls.__dict__: + tvars = typing._collect_type_vars(cls.__orig_bases__) + # Look for Generic[T1, ..., Tn] or Protocol[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...] and/or Protocol[...]. + gvars = None + for base in cls.__orig_bases__: + if (isinstance(base, typing._GenericAlias) and + base.__origin__ in (typing.Generic, Protocol)): + # for error messages + the_base = base.__origin__.__name__ + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...]" + " and/or Protocol[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + s_vars = ', '.join(str(t) for t in tvars if t not in gvarset) + s_args = ', '.join(str(g) for g in gvars) + raise TypeError(f"Some type variables ({s_vars}) are" + f" not listed in {the_base}[{s_args}]") + tvars = gvars + cls.__parameters__ = tuple(tvars) + + +# 3.8+ +if hasattr(typing, 'Protocol'): + Protocol = typing.Protocol +# 3.7 +else: + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + + class _ProtocolMeta(abc.ABCMeta): # noqa: B024 + # This metaclass is a bit unfortunate and exists only because of the lack + # of __instancehook__. + def __instancecheck__(cls, instance): + # We need this method for situations where attributes are + # assigned in __init__. + if ((not getattr(cls, '_is_protocol', False) or + _is_callable_members_only(cls)) and + issubclass(instance.__class__, cls)): + return True + if cls._is_protocol: + if all(hasattr(instance, attr) and + (not callable(getattr(cls, attr, None)) or + getattr(instance, attr) is not None) + for attr in _get_protocol_attrs(cls)): + return True + return super().__instancecheck__(instance) + + class Protocol(metaclass=_ProtocolMeta): + # There is quite a lot of overlapping code with typing.Generic. + # Unfortunately it is hard to avoid this while these live in two different + # modules. The duplicated code will be removed when Protocol is moved to typing. + """Base class for protocol classes. Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing), for example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing_extensions.runtime act as simple-minded runtime protocol that checks + only the presence of given attributes, ignoring their type signatures. + + Protocol classes can be generic, they are defined as:: + + class GenProto(Protocol[T]): + def meth(self) -> T: + ... + """ + __slots__ = () + _is_protocol = True + + def __new__(cls, *args, **kwds): + if cls is Protocol: + raise TypeError("Type Protocol cannot be instantiated; " + "it can only be used as a base class") + return super().__new__(cls) + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple): + params = (params,) + if not params and cls is not typing.Tuple: + raise TypeError( + f"Parameter list to {cls.__qualname__}[...] cannot be empty") + msg = "Parameters to generic types must be types." + params = tuple(typing._type_check(p, msg) for p in params) # noqa + if cls is Protocol: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, typing.TypeVar) for p in params): + i = 0 + while isinstance(params[i], typing.TypeVar): + i += 1 + raise TypeError( + "Parameters to Protocol[...] must all be type variables." + f" Parameter {i + 1} is {params[i]}") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Protocol[...] must all be unique") + else: + # Subscripting a regular Generic subclass. + _check_generic(cls, params, len(cls.__parameters__)) + return typing._GenericAlias(cls, params) + + def __init_subclass__(cls, *args, **kwargs): + if '__orig_bases__' in cls.__dict__: + error = typing.Generic in cls.__orig_bases__ + else: + error = typing.Generic in cls.__bases__ + if error: + raise TypeError("Cannot inherit from plain Generic") + _maybe_adjust_parameters(cls) + + # Determine if this is a protocol or a concrete subclass. + if not cls.__dict__.get('_is_protocol', None): + cls._is_protocol = any(b is Protocol for b in cls.__bases__) + + # Set (or override) the protocol subclass hook. + def _proto_hook(other): + if not cls.__dict__.get('_is_protocol', None): + return NotImplemented + if not getattr(cls, '_is_runtime_protocol', False): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Instance and class checks can only be used with" + " @runtime protocols") + if not _is_callable_members_only(cls): + if sys._getframe(2).f_globals['__name__'] in ['abc', 'functools']: + return NotImplemented + raise TypeError("Protocols with non-method members" + " don't support issubclass()") + if not isinstance(other, type): + # Same error as for issubclass(1, int) + raise TypeError('issubclass() arg 1 must be a class') + for attr in _get_protocol_attrs(cls): + for base in other.__mro__: + if attr in base.__dict__: + if base.__dict__[attr] is None: + return NotImplemented + break + annotations = getattr(base, '__annotations__', {}) + if (isinstance(annotations, typing.Mapping) and + attr in annotations and + isinstance(other, _ProtocolMeta) and + other._is_protocol): + break + else: + return NotImplemented + return True + if '__subclasshook__' not in cls.__dict__: + cls.__subclasshook__ = _proto_hook + + # We have nothing more to do for non-protocols. + if not cls._is_protocol: + return + + # Check consistency of bases. + for base in cls.__bases__: + if not (base in (object, typing.Generic) or + base.__module__ == 'collections.abc' and + base.__name__ in _PROTO_WHITELIST or + isinstance(base, _ProtocolMeta) and base._is_protocol): + raise TypeError('Protocols can only inherit from other' + f' protocols, got {repr(base)}') + cls.__init__ = _no_init + + +# 3.8+ +if hasattr(typing, 'runtime_checkable'): + runtime_checkable = typing.runtime_checkable +# 3.7 +else: + def runtime_checkable(cls): + """Mark a protocol class as a runtime protocol, so that it + can be used with isinstance() and issubclass(). Raise TypeError + if applied to a non-protocol class. + + This allows a simple-minded structural check very similar to the + one-offs in collections.abc such as Hashable. + """ + if not isinstance(cls, _ProtocolMeta) or not cls._is_protocol: + raise TypeError('@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') + cls._is_runtime_protocol = True + return cls + + +# Exists for backwards compatibility. +runtime = runtime_checkable + + +# 3.8+ +if hasattr(typing, 'SupportsIndex'): + SupportsIndex = typing.SupportsIndex +# 3.7 +else: + @runtime_checkable + class SupportsIndex(Protocol): + __slots__ = () + + @abc.abstractmethod + def __index__(self) -> int: + pass + + +if hasattr(typing, "Required"): + # The standard library TypedDict in Python 3.8 does not store runtime information + # about which (if any) keys are optional. See https://bugs.python.org/issue38834 + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 + # The standard library TypedDict below Python 3.11 does not store runtime + # information about optional and required keys when using Required or NotRequired. + # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11. + TypedDict = typing.TypedDict + _TypedDictMeta = typing._TypedDictMeta + is_typeddict = typing.is_typeddict +else: + def _check_fails(cls, other): + try: + if sys._getframe(1).f_globals['__name__'] not in ['abc', + 'functools', + 'typing']: + # Typed dicts are only for static structural subtyping. + raise TypeError('TypedDict does not support instance and class checks') + except (AttributeError, ValueError): + pass + return False + + def _dict_new(*args, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + return dict(*args, **kwargs) + + _dict_new.__text_signature__ = '($cls, _typename, _fields=None, /, **kwargs)' + + def _typeddict_new(*args, total=True, **kwargs): + if not args: + raise TypeError('TypedDict.__new__(): not enough arguments') + _, args = args[0], args[1:] # allow the "cls" keyword be passed + if args: + typename, args = args[0], args[1:] # allow the "_typename" keyword be passed + elif '_typename' in kwargs: + typename = kwargs.pop('_typename') + import warnings + warnings.warn("Passing '_typename' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + raise TypeError("TypedDict.__new__() missing 1 required positional " + "argument: '_typename'") + if args: + try: + fields, = args # allow the "_fields" keyword be passed + except ValueError: + raise TypeError('TypedDict.__new__() takes from 2 to 3 ' + f'positional arguments but {len(args) + 2} ' + 'were given') + elif '_fields' in kwargs and len(kwargs) == 1: + fields = kwargs.pop('_fields') + import warnings + warnings.warn("Passing '_fields' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + fields = None + + if fields is None: + fields = kwargs + elif kwargs: + raise TypeError("TypedDict takes either a dict or keyword arguments," + " but not both") + + ns = {'__annotations__': dict(fields)} + try: + # Setting correct module is necessary to make typed dict classes pickleable. + ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + + return _TypedDictMeta(typename, (), ns, total=total) + + _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' + ' /, *, total=True, **kwargs)') + + class _TypedDictMeta(type): + def __init__(cls, name, bases, ns, total=True): + super().__init__(name, bases, ns) + + def __new__(cls, name, bases, ns, total=True): + # Create new typed dict class object. + # This method is called directly when TypedDict is subclassed, + # or via _typeddict_new when TypedDict is instantiated. This way + # TypedDict supports all three syntaxes described in its docstring. + # Subclasses and instances of TypedDict return actual dictionaries + # via _dict_new. + ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new + # Don't insert typing.Generic into __bases__ here, + # or Generic.__init_subclass__ will raise TypeError + # in the super().__new__() call. + # Instead, monkey-patch __bases__ onto the class after it's been created. + tp_dict = super().__new__(cls, name, (dict,), ns) + + if any(issubclass(base, typing.Generic) for base in bases): + tp_dict.__bases__ = (typing.Generic, dict) + _maybe_adjust_parameters(tp_dict) + + annotations = {} + own_annotations = ns.get('__annotations__', {}) + msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type" + own_annotations = { + n: typing._type_check(tp, msg) for n, tp in own_annotations.items() + } + required_keys = set() + optional_keys = set() + + for base in bases: + annotations.update(base.__dict__.get('__annotations__', {})) + required_keys.update(base.__dict__.get('__required_keys__', ())) + optional_keys.update(base.__dict__.get('__optional_keys__', ())) + + annotations.update(own_annotations) + for annotation_key, annotation_type in own_annotations.items(): + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(annotation_key) + elif annotation_origin is NotRequired: + optional_keys.add(annotation_key) + elif total: + required_keys.add(annotation_key) + else: + optional_keys.add(annotation_key) + + tp_dict.__annotations__ = annotations + tp_dict.__required_keys__ = frozenset(required_keys) + tp_dict.__optional_keys__ = frozenset(optional_keys) + if not hasattr(tp_dict, '__total__'): + tp_dict.__total__ = total + return tp_dict + + __instancecheck__ = __subclasscheck__ = _check_fails + + TypedDict = _TypedDictMeta('TypedDict', (dict,), {}) + TypedDict.__module__ = __name__ + TypedDict.__doc__ = \ + """A simple typed name space. At runtime it is equivalent to a plain dict. + + TypedDict creates a dictionary type that expects all of its + instances to have a certain set of keys, with each key + associated with a value of a consistent type. This expectation + is not checked at runtime but is only enforced by type checkers. + Usage:: + + class Point2D(TypedDict): + x: int + y: int + label: str + + a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK + b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check + + assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first') + + The type info can be accessed via the Point2D.__annotations__ dict, and + the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets. + TypedDict supports two additional equivalent forms:: + + Point2D = TypedDict('Point2D', x=int, y=int, label=str) + Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str}) + + The class syntax is only supported in Python 3.6+, while two other + syntax forms work for Python 2.7 and 3.2+ + """ + + if hasattr(typing, "_TypedDictMeta"): + _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta) + else: + _TYPEDDICT_TYPES = (_TypedDictMeta,) + + def is_typeddict(tp): + """Check if an annotation is a TypedDict class + + For example:: + class Film(TypedDict): + title: str + year: int + + is_typeddict(Film) # => True + is_typeddict(Union[list, str]) # => False + """ + return isinstance(tp, tuple(_TYPEDDICT_TYPES)) + + +if hasattr(typing, "assert_type"): + assert_type = typing.assert_type + +else: + def assert_type(__val, __typ): + """Assert (to the type checker) that the value is of the given type. + + When the type checker encounters a call to assert_type(), it + emits an error if the value is not of the specified type:: + + def greet(name: str) -> None: + assert_type(name, str) # ok + assert_type(name, int) # type checker error + + At runtime this returns the first argument unchanged and otherwise + does nothing. + """ + return __val + + +if hasattr(typing, "Required"): + get_type_hints = typing.get_type_hints +else: + import functools + import types + + # replaces _strip_annotations() + def _strip_extras(t): + """Strips Annotated, Required and NotRequired from a given type.""" + if isinstance(t, _AnnotatedAlias): + return _strip_extras(t.__origin__) + if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired): + return _strip_extras(t.__args__[0]) + if isinstance(t, typing._GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return t.copy_with(stripped_args) + if hasattr(types, "GenericAlias") and isinstance(t, types.GenericAlias): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return types.GenericAlias(t.__origin__, stripped_args) + if hasattr(types, "UnionType") and isinstance(t, types.UnionType): + stripped_args = tuple(_strip_extras(a) for a in t.__args__) + if stripped_args == t.__args__: + return t + return functools.reduce(operator.or_, stripped_args) + + return t + + def get_type_hints(obj, globalns=None, localns=None, include_extras=False): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, adds Optional[t] if a + default value equal to None is set and recursively replaces all + 'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T' + (unless 'include_extras=True'). + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + if hasattr(typing, "Annotated"): + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) + else: + hint = typing.get_type_hints(obj, globalns=globalns, localns=localns) + if include_extras: + return hint + return {k: _strip_extras(t) for k, t in hint.items()} + + +# Python 3.9+ has PEP 593 (Annotated) +if hasattr(typing, 'Annotated'): + Annotated = typing.Annotated + # Not exported and not a public API, but needed for get_origin() and get_args() + # to work. + _AnnotatedAlias = typing._AnnotatedAlias +# 3.7-3.8 +else: + class _AnnotatedAlias(typing._GenericAlias, _root=True): + """Runtime representation of an annotated type. + + At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't' + with extra annotations. The alias behaves like a normal typing alias, + instantiating is the same as instantiating the underlying type, binding + it to types is also the same. + """ + def __init__(self, origin, metadata): + if isinstance(origin, _AnnotatedAlias): + metadata = origin.__metadata__ + metadata + origin = origin.__origin__ + super().__init__(origin, origin) + self.__metadata__ = metadata + + def copy_with(self, params): + assert len(params) == 1 + new_type = params[0] + return _AnnotatedAlias(new_type, self.__metadata__) + + def __repr__(self): + return (f"typing_extensions.Annotated[{typing._type_repr(self.__origin__)}, " + f"{', '.join(repr(a) for a in self.__metadata__)}]") + + def __reduce__(self): + return operator.getitem, ( + Annotated, (self.__origin__,) + self.__metadata__ + ) + + def __eq__(self, other): + if not isinstance(other, _AnnotatedAlias): + return NotImplemented + if self.__origin__ != other.__origin__: + return False + return self.__metadata__ == other.__metadata__ + + def __hash__(self): + return hash((self.__origin__, self.__metadata__)) + + class Annotated: + """Add context specific metadata to a type. + + Example: Annotated[int, runtime_check.Unsigned] indicates to the + hypothetical runtime_check module that this type is an unsigned int. + Every other consumer of this type can ignore this metadata and treat + this type as int. + + The first argument to Annotated must be a valid type (and will be in + the __origin__ field), the remaining arguments are kept as a tuple in + the __extra__ field. + + Details: + + - It's an error to call `Annotated` with less than two arguments. + - Nested Annotated are flattened:: + + Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] + + - Instantiating an annotated type is equivalent to instantiating the + underlying type:: + + Annotated[C, Ann1](5) == C(5) + + - Annotated can be used as a generic type alias:: + + Optimized = Annotated[T, runtime.Optimize()] + Optimized[int] == Annotated[int, runtime.Optimize()] + + OptimizedList = Annotated[List[T], runtime.Optimize()] + OptimizedList[int] == Annotated[List[int], runtime.Optimize()] + """ + + __slots__ = () + + def __new__(cls, *args, **kwargs): + raise TypeError("Type Annotated cannot be instantiated.") + + @typing._tp_cache + def __class_getitem__(cls, params): + if not isinstance(params, tuple) or len(params) < 2: + raise TypeError("Annotated[...] should be used " + "with at least two arguments (a type and an " + "annotation).") + allowed_special_forms = (ClassVar, Final) + if get_origin(params[0]) in allowed_special_forms: + origin = params[0] + else: + msg = "Annotated[t, ...]: t must be a type." + origin = typing._type_check(params[0], msg) + metadata = tuple(params[1:]) + return _AnnotatedAlias(origin, metadata) + + def __init_subclass__(cls, *args, **kwargs): + raise TypeError( + f"Cannot subclass {cls.__module__}.Annotated" + ) + +# Python 3.8 has get_origin() and get_args() but those implementations aren't +# Annotated-aware, so we can't use those. Python 3.9's versions don't support +# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do. +if sys.version_info[:2] >= (3, 10): + get_origin = typing.get_origin + get_args = typing.get_args +# 3.7-3.9 +else: + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = typing._GenericAlias + try: + # 3.9+ + from typing import GenericAlias as _typing_GenericAlias + except ImportError: + _typing_GenericAlias = typing._GenericAlias + + def get_origin(tp): + """Get the unsubscripted version of a type. + + This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar + and Annotated. Return None for unsupported types. Examples:: + + get_origin(Literal[42]) is Literal + get_origin(int) is None + get_origin(ClassVar[int]) is ClassVar + get_origin(Generic) is Generic + get_origin(Generic[T]) is Generic + get_origin(Union[T, int]) is Union + get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P + """ + if isinstance(tp, _AnnotatedAlias): + return Annotated + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): + return tp.__origin__ + if tp is typing.Generic: + return typing.Generic + return None + + def get_args(tp): + """Get type arguments with all substitutions performed. + + For unions, basic simplifications used by Union constructor are performed. + Examples:: + get_args(Dict[str, int]) == (str, int) + get_args(int) == () + get_args(Union[int, Union[T, int], str][int]) == (int, str) + get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int]) + get_args(Callable[[], T][int]) == ([], int) + """ + if isinstance(tp, _AnnotatedAlias): + return (tp.__origin__,) + tp.__metadata__ + if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): + if getattr(tp, "_special", False): + return () + res = tp.__args__ + if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: + res = (list(res[:-1]), res[-1]) + return res + return () + + +# 3.10+ +if hasattr(typing, 'TypeAlias'): + TypeAlias = typing.TypeAlias +# 3.9 +elif sys.version_info[:2] >= (3, 9): + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeAliasForm + def TypeAlias(self, parameters): + """Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example above. + """ + raise TypeError(f"{self} is not subscriptable") +# 3.7-3.8 +else: + class _TypeAliasForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + TypeAlias = _TypeAliasForm('TypeAlias', + doc="""Special marker indicating that an assignment should + be recognized as a proper type alias definition by type + checkers. + + For example:: + + Predicate: TypeAlias = Callable[..., bool] + + It's invalid when used anywhere except as in the example + above.""") + + +class _DefaultMixin: + """Mixin for TypeVarLike defaults.""" + + __slots__ = () + + def __init__(self, default): + if isinstance(default, (tuple, list)): + self.__default__ = tuple((typing._type_check(d, "Default must be a type") + for d in default)) + elif default: + self.__default__ = typing._type_check(default, "Default must be a type") + else: + self.__default__ = None + + +# Add default and infer_variance parameters from PEP 696 and 695 +class TypeVar(typing.TypeVar, _DefaultMixin, _root=True): + """Type variable.""" + + __module__ = 'typing' + + def __init__(self, name, *constraints, bound=None, + covariant=False, contravariant=False, + default=None, infer_variance=False): + super().__init__(name, *constraints, bound=bound, covariant=covariant, + contravariant=contravariant) + _DefaultMixin.__init__(self, default) + self.__infer_variance__ = infer_variance + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +# 3.7-3.9 +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.args" + + def __eq__(self, other): + if not isinstance(other, ParamSpecArgs): + return NotImplemented + return self.__origin__ == other.__origin__ + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return f"{self.__origin__.__name__}.kwargs" + + def __eq__(self, other): + if not isinstance(other, ParamSpecKwargs): + return NotImplemented + return self.__origin__ == other.__origin__ + +# 3.10+ +if hasattr(typing, 'ParamSpec'): + + # Add default Parameter - PEP 696 + class ParamSpec(typing.ParamSpec, _DefaultMixin, _root=True): + """Parameter specification variable.""" + + __module__ = 'typing' + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + default=None): + super().__init__(name, bound=bound, covariant=covariant, + contravariant=contravariant) + _DefaultMixin.__init__(self, default) + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + +# 3.7-3.9 +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list, _DefaultMixin): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False, + default=None): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + _DefaultMixin.__init__(self, default) + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + +# 3.7-3.9 +if not hasattr(typing, 'Concatenate'): + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + __class__ = typing._GenericAlias + + # Flag in 3.8. + _special = False + + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return (f'{_type_repr(self.__origin__)}' + f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]') + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple( + tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec)) + ) + + +# 3.7-3.9 +@typing._tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(typing._type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + +# 3.10+ +if hasattr(typing, 'Concatenate'): + Concatenate = typing.Concatenate + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa +# 3.9 +elif sys.version_info[:2] >= (3, 9): + @_TypeAliasForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) +# 3.7-8 +else: + class _ConcatenateForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateForm( + 'Concatenate', + doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """) + +# 3.10+ +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +# 3.9 +elif sys.version_info[:2] >= (3, 9): + class _TypeGuardForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeGuardForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, f'{self} accepts only a single type.') + return typing._GenericAlias(self, (item,)) +# 3.7-3.8 +else: + class _TypeGuardForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type') + return typing._GenericAlias(self, (item,)) + + TypeGuard = _TypeGuardForm( + 'TypeGuard', + doc="""Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """) + + +# Vendored from cpython typing._SpecialFrom +class _SpecialForm(typing._Final, _root=True): + __slots__ = ('_name', '__doc__', '_getitem') + + def __init__(self, getitem): + self._getitem = getitem + self._name = getitem.__name__ + self.__doc__ = getitem.__doc__ + + def __getattr__(self, item): + if item in {'__name__', '__qualname__'}: + return self._name + + raise AttributeError(item) + + def __mro_entries__(self, bases): + raise TypeError(f"Cannot subclass {self!r}") + + def __repr__(self): + return f'typing_extensions.{self._name}' + + def __reduce__(self): + return self._name + + def __call__(self, *args, **kwds): + raise TypeError(f"Cannot instantiate {self!r}") + + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __instancecheck__(self, obj): + raise TypeError(f"{self} cannot be used with isinstance()") + + def __subclasscheck__(self, cls): + raise TypeError(f"{self} cannot be used with issubclass()") + + @typing._tp_cache + def __getitem__(self, parameters): + return self._getitem(self, parameters) + + +if hasattr(typing, "LiteralString"): + LiteralString = typing.LiteralString +else: + @_SpecialForm + def LiteralString(self, params): + """Represents an arbitrary literal string. + + Example:: + + from typing_extensions import LiteralString + + def query(sql: LiteralString) -> ...: + ... + + query("SELECT * FROM table") # ok + query(f"SELECT * FROM {input()}") # not ok + + See PEP 675 for details. + + """ + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Self"): + Self = typing.Self +else: + @_SpecialForm + def Self(self, params): + """Used to spell the type of "self" in classes. + + Example:: + + from typing import Self + + class ReturnsSelf: + def parse(self, data: bytes) -> Self: + ... + return self + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, "Never"): + Never = typing.Never +else: + @_SpecialForm + def Never(self, params): + """The bottom type, a type that has no members. + + This can be used to define a function that should never be + called, or a function that never returns:: + + from typing_extensions import Never + + def never_call_me(arg: Never) -> None: + pass + + def int_or_str(arg: int | str) -> None: + never_call_me(arg) # type checker error + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + never_call_me(arg) # ok, arg is of type Never + + """ + + raise TypeError(f"{self} is not subscriptable") + + +if hasattr(typing, 'Required'): + Required = typing.Required + NotRequired = typing.NotRequired +elif sys.version_info[:2] >= (3, 9): + class _ExtensionsSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_ExtensionsSpecialForm + def Required(self, parameters): + """A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + @_ExtensionsSpecialForm + def NotRequired(self, parameters): + """A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + +else: + class _RequiredForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return typing._GenericAlias(self, (item,)) + + Required = _RequiredForm( + 'Required', + doc="""A special typing construct to mark a key of a total=False TypedDict + as required. For example: + + class Movie(TypedDict, total=False): + title: Required[str] + year: int + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + + There is no runtime checking that a required key is actually provided + when instantiating a related TypedDict. + """) + NotRequired = _RequiredForm( + 'NotRequired', + doc="""A special typing construct to mark a key of a TypedDict as + potentially missing. For example: + + class Movie(TypedDict): + title: str + year: NotRequired[int] + + m = Movie( + title='The Matrix', # typechecker error if key is omitted + year=1999, + ) + """) + + +if hasattr(typing, "Unpack"): # 3.11+ + Unpack = typing.Unpack +elif sys.version_info[:2] >= (3, 9): + class _UnpackSpecialForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + @_UnpackSpecialForm + def Unpack(self, parameters): + """A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """ + item = typing._type_check(parameters, f'{self._name} accepts only a single type.') + return _UnpackAlias(self, (item,)) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + +else: + class _UnpackAlias(typing._GenericAlias, _root=True): + __class__ = typing.TypeVar + + class _UnpackForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + f'{self._name} accepts only a single type.') + return _UnpackAlias(self, (item,)) + + Unpack = _UnpackForm( + 'Unpack', + doc="""A special typing construct to unpack a variadic type. For example: + + Shape = TypeVarTuple('Shape') + Batch = NewType('Batch', int) + + def add_batch_axis( + x: Array[Unpack[Shape]] + ) -> Array[Batch, Unpack[Shape]]: ... + + """) + + def _is_unpack(obj): + return isinstance(obj, _UnpackAlias) + + +if hasattr(typing, "TypeVarTuple"): # 3.11+ + + # Add default Parameter - PEP 696 + class TypeVarTuple(typing.TypeVarTuple, _DefaultMixin, _root=True): + """Type variable tuple.""" + + def __init__(self, name, *, default=None): + super().__init__(name) + _DefaultMixin.__init__(self, default) + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + +else: + class TypeVarTuple(_DefaultMixin): + """Type variable tuple. + + Usage:: + + Ts = TypeVarTuple('Ts') + + In the same way that a normal type variable is a stand-in for a single + type such as ``int``, a type variable *tuple* is a stand-in for a *tuple* + type such as ``Tuple[int, str]``. + + Type variable tuples can be used in ``Generic`` declarations. + Consider the following example:: + + class Array(Generic[*Ts]): ... + + The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``, + where ``T1`` and ``T2`` are type variables. To use these type variables + as type parameters of ``Array``, we must *unpack* the type variable tuple using + the star operator: ``*Ts``. The signature of ``Array`` then behaves + as if we had simply written ``class Array(Generic[T1, T2]): ...``. + In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows + us to parameterise the class with an *arbitrary* number of type parameters. + + Type variable tuples can be used anywhere a normal ``TypeVar`` can. + This includes class definitions, as shown above, as well as function + signatures and variable annotations:: + + class Array(Generic[*Ts]): + + def __init__(self, shape: Tuple[*Ts]): + self._shape: Tuple[*Ts] = shape + + def get_shape(self) -> Tuple[*Ts]: + return self._shape + + shape = (Height(480), Width(640)) + x: Array[Height, Width] = Array(shape) + y = abs(x) # Inferred type is Array[Height, Width] + z = x + x # ... is Array[Height, Width] + x.get_shape() # ... is tuple[Height, Width] + + """ + + # Trick Generic __parameters__. + __class__ = typing.TypeVar + + def __iter__(self): + yield self.__unpacked__ + + def __init__(self, name, *, default=None): + self.__name__ = name + _DefaultMixin.__init__(self, default) + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + self.__unpacked__ = Unpack[self] + + def __repr__(self): + return self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + def __init_subclass__(self, *args, **kwds): + if '_root' not in kwds: + raise TypeError("Cannot subclass special typing classes") + + +if hasattr(typing, "reveal_type"): + reveal_type = typing.reveal_type +else: + def reveal_type(__obj: T) -> T: + """Reveal the inferred type of a variable. + + When a static type checker encounters a call to ``reveal_type()``, + it will emit the inferred type of the argument:: + + x: int = 1 + reveal_type(x) + + Running a static type checker (e.g., ``mypy``) on this example + will produce output similar to 'Revealed type is "builtins.int"'. + + At runtime, the function prints the runtime type of the + argument and returns it unchanged. + + """ + print(f"Runtime type is {type(__obj).__name__!r}", file=sys.stderr) + return __obj + + +if hasattr(typing, "assert_never"): + assert_never = typing.assert_never +else: + def assert_never(__arg: Never) -> Never: + """Assert to the type checker that a line of code is unreachable. + + Example:: + + def int_or_str(arg: int | str) -> None: + match arg: + case int(): + print("It's an int") + case str(): + print("It's a str") + case _: + assert_never(arg) + + If a type checker finds that a call to assert_never() is + reachable, it will emit an error. + + At runtime, this throws an exception when called. + + """ + raise AssertionError("Expected code to be unreachable") + + +if hasattr(typing, 'dataclass_transform'): + dataclass_transform = typing.dataclass_transform +else: + def dataclass_transform( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_specifiers: typing.Tuple[ + typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]], + ... + ] = (), + **kwargs: typing.Any, + ) -> typing.Callable[[T], T]: + """Decorator that marks a function, class, or metaclass as providing + dataclass-like behavior. + + Example: + + from typing_extensions import dataclass_transform + + _T = TypeVar("_T") + + # Used on a decorator function + @dataclass_transform() + def create_model(cls: type[_T]) -> type[_T]: + ... + return cls + + @create_model + class CustomerModel: + id: int + name: str + + # Used on a base class + @dataclass_transform() + class ModelBase: ... + + class CustomerModel(ModelBase): + id: int + name: str + + # Used on a metaclass + @dataclass_transform() + class ModelMeta(type): ... + + class ModelBase(metaclass=ModelMeta): ... + + class CustomerModel(ModelBase): + id: int + name: str + + Each of the ``CustomerModel`` classes defined in this example will now + behave similarly to a dataclass created with the ``@dataclasses.dataclass`` + decorator. For example, the type checker will synthesize an ``__init__`` + method. + + The arguments to this decorator can be used to customize this behavior: + - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be + True or False if it is omitted by the caller. + - ``order_default`` indicates whether the ``order`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``kw_only_default`` indicates whether the ``kw_only`` parameter is + assumed to be True or False if it is omitted by the caller. + - ``field_specifiers`` specifies a static list of supported classes + or functions that describe fields, similar to ``dataclasses.field()``. + + At runtime, this decorator records its arguments in the + ``__dataclass_transform__`` attribute on the decorated object. + + See PEP 681 for details. + + """ + def decorator(cls_or_fn): + cls_or_fn.__dataclass_transform__ = { + "eq_default": eq_default, + "order_default": order_default, + "kw_only_default": kw_only_default, + "field_specifiers": field_specifiers, + "kwargs": kwargs, + } + return cls_or_fn + return decorator + + +if hasattr(typing, "override"): + override = typing.override +else: + _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any]) + + def override(__arg: _F) -> _F: + """Indicate that a method is intended to override a method in a base class. + + Usage: + + class Base: + def method(self) -> None: ... + pass + + class Child(Base): + @override + def method(self) -> None: + super().method() + + When this decorator is applied to a method, the type checker will + validate that it overrides a method with the same name on a base class. + This helps prevent bugs that may occur when a base class is changed + without an equivalent change to a child class. + + See PEP 698 for details. + + """ + return __arg + + +# We have to do some monkey patching to deal with the dual nature of +# Unpack/TypeVarTuple: +# - We want Unpack to be a kind of TypeVar so it gets accepted in +# Generic[Unpack[Ts]] +# - We want it to *not* be treated as a TypeVar for the purposes of +# counting generic parameters, so that when we subscript a generic, +# the runtime doesn't try to substitute the Unpack with the subscripted type. +if not hasattr(typing, "TypeVarTuple"): + typing._collect_type_vars = _collect_type_vars + typing._check_generic = _check_generic + + +# Backport typing.NamedTuple as it exists in Python 3.11. +# In 3.11, the ability to define generic `NamedTuple`s was supported. +# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8. +if sys.version_info >= (3, 11): + NamedTuple = typing.NamedTuple +else: + def _caller(): + try: + return sys._getframe(2).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): # For platforms without _getframe() + return None + + def _make_nmtuple(name, types, module, defaults=()): + fields = [n for n, t in types] + annotations = {n: typing._type_check(t, f"field {n} annotation must be a type") + for n, t in types} + nm_tpl = collections.namedtuple(name, fields, + defaults=defaults, module=module) + nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations + # The `_field_types` attribute was removed in 3.9; + # in earlier versions, it is the same as the `__annotations__` attribute + if sys.version_info < (3, 9): + nm_tpl._field_types = annotations + return nm_tpl + + _prohibited_namedtuple_fields = typing._prohibited + _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'}) + + class _NamedTupleMeta(type): + def __new__(cls, typename, bases, ns): + assert _NamedTuple in bases + for base in bases: + if base is not _NamedTuple and base is not typing.Generic: + raise TypeError( + 'can only inherit from a NamedTuple type and Generic') + bases = tuple(tuple if base is _NamedTuple else base for base in bases) + types = ns.get('__annotations__', {}) + default_names = [] + for field_name in types: + if field_name in ns: + default_names.append(field_name) + elif default_names: + raise TypeError(f"Non-default namedtuple field {field_name} " + f"cannot follow default field" + f"{'s' if len(default_names) > 1 else ''} " + f"{', '.join(default_names)}") + nm_tpl = _make_nmtuple( + typename, types.items(), + defaults=[ns[n] for n in default_names], + module=ns['__module__'] + ) + nm_tpl.__bases__ = bases + if typing.Generic in bases: + class_getitem = typing.Generic.__class_getitem__.__func__ + nm_tpl.__class_getitem__ = classmethod(class_getitem) + # update from user namespace without overriding special namedtuple attributes + for key in ns: + if key in _prohibited_namedtuple_fields: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special_namedtuple_fields and key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + if typing.Generic in bases: + nm_tpl.__init_subclass__() + return nm_tpl + + def NamedTuple(__typename, __fields=None, **kwargs): + if __fields is None: + __fields = kwargs.items() + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + return _make_nmtuple(__typename, __fields, module=_caller()) + + NamedTuple.__doc__ = typing.NamedTuple.__doc__ + _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {}) + + # On 3.8+, alter the signature so that it matches typing.NamedTuple. + # The signature of typing.NamedTuple on >=3.8 is invalid syntax in Python 3.7, + # so just leave the signature as it is on 3.7. + if sys.version_info >= (3, 8): + NamedTuple.__text_signature__ = '(typename, fields=None, /, **kwargs)' + + def _namedtuple_mro_entries(bases): + assert NamedTuple in bases + return (_NamedTuple,) + + NamedTuple.__mro_entries__ = _namedtuple_mro_entries diff --git a/conda_lock/_vendor/poetry/core/_vendor/vendor.txt b/conda_lock/_vendor/poetry/core/_vendor/vendor.txt index 13de1ee1b..a36782df3 100644 --- a/conda_lock/_vendor/poetry/core/_vendor/vendor.txt +++ b/conda_lock/_vendor/poetry/core/_vendor/vendor.txt @@ -1,9 +1,9 @@ -attrs==20.3.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -jsonschema==3.2.0 -lark-parser==0.9.0 -packaging==20.9; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -pyparsing==2.4.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -pyrsistent==0.16.1; python_version >= "2.7" -six==1.15.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "2.7" -tomlkit==0.7.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -typing-extensions==3.7.4.3; python_version >= "3.6" and python_version < "3.8" +attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0" +jsonschema==4.17.0 ; python_version >= "3.7" and python_version < "4.0" +lark==1.1.4 ; python_version >= "3.7" and python_version < "4.0" +packaging==21.3 ; python_version >= "3.7" and python_version < "4.0" +pkgutil-resolve-name==1.3.10 ; python_version >= "3.7" and python_version < "3.9" +pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0" +pyrsistent==0.19.2 ; python_version >= "3.7" and python_version < "4.0" +tomlkit==0.11.6 ; python_version >= "3.7" and python_version < "4.0" +typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0" diff --git a/conda_lock/_vendor/poetry/core/constraints/__init__.py b/conda_lock/_vendor/poetry/core/constraints/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/__init__.py b/conda_lock/_vendor/poetry/core/constraints/generic/__init__.py new file mode 100644 index 000000000..7c953e335 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/__init__.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.generic.any_constraint import AnyConstraint +from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint +from conda_lock._vendor.poetry.core.constraints.generic.constraint import Constraint +from conda_lock._vendor.poetry.core.constraints.generic.empty_constraint import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.generic.multi_constraint import MultiConstraint +from conda_lock._vendor.poetry.core.constraints.generic.parser import parse_constraint +from conda_lock._vendor.poetry.core.constraints.generic.union_constraint import UnionConstraint + + +__all__ = [ + "AnyConstraint", + "BaseConstraint", + "Constraint", + "EmptyConstraint", + "MultiConstraint", + "UnionConstraint", + "parse_constraint", +] diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/any_constraint.py b/conda_lock/_vendor/poetry/core/constraints/generic/any_constraint.py new file mode 100644 index 000000000..9b00b5f67 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/any_constraint.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint +from conda_lock._vendor.poetry.core.constraints.generic.empty_constraint import EmptyConstraint + + +class AnyConstraint(BaseConstraint): + def allows(self, other: BaseConstraint) -> bool: + return True + + def allows_all(self, other: BaseConstraint) -> bool: + return True + + def allows_any(self, other: BaseConstraint) -> bool: + return True + + def difference(self, other: BaseConstraint) -> BaseConstraint: + if other.is_any(): + return EmptyConstraint() + + raise ValueError("Unimplemented constraint difference") + + def intersect(self, other: BaseConstraint) -> BaseConstraint: + return other + + def union(self, other: BaseConstraint) -> AnyConstraint: + return AnyConstraint() + + def is_any(self) -> bool: + return True + + def is_empty(self) -> bool: + return False + + def __str__(self) -> str: + return "*" + + def __eq__(self, other: object) -> bool: + return isinstance(other, BaseConstraint) and other.is_any() + + def __hash__(self) -> int: + return hash("any") diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/base_constraint.py b/conda_lock/_vendor/poetry/core/constraints/generic/base_constraint.py new file mode 100644 index 000000000..df5826994 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/base_constraint.py @@ -0,0 +1,39 @@ +from __future__ import annotations + + +class BaseConstraint: + def allows(self, other: BaseConstraint) -> bool: + raise NotImplementedError() + + def allows_all(self, other: BaseConstraint) -> bool: + raise NotImplementedError() + + def allows_any(self, other: BaseConstraint) -> bool: + raise NotImplementedError() + + def difference(self, other: BaseConstraint) -> BaseConstraint: + raise NotImplementedError() + + def intersect(self, other: BaseConstraint) -> BaseConstraint: + raise NotImplementedError() + + def union(self, other: BaseConstraint) -> BaseConstraint: + raise NotImplementedError() + + def is_any(self) -> bool: + return False + + def is_empty(self) -> bool: + return False + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {str(self)}>" + + def __str__(self) -> str: + raise NotImplementedError() + + def __hash__(self) -> int: + raise NotImplementedError() + + def __eq__(self, other: object) -> bool: + raise NotImplementedError() diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/constraint.py b/conda_lock/_vendor/poetry/core/constraints/generic/constraint.py similarity index 56% rename from conda_lock/_vendor/poetry/core/packages/constraints/constraint.py rename to conda_lock/_vendor/poetry/core/constraints/generic/constraint.py index 1ebe915f5..985abf88b 100644 --- a/conda_lock/_vendor/poetry/core/packages/constraints/constraint.py +++ b/conda_lock/_vendor/poetry/core/constraints/generic/constraint.py @@ -1,19 +1,13 @@ -import operator - -from typing import TYPE_CHECKING -from typing import Any -from typing import Union - -from .base_constraint import BaseConstraint -from .empty_constraint import EmptyConstraint +from __future__ import annotations +import operator -if TYPE_CHECKING: - from . import ConstraintTypes # noqa +from conda_lock._vendor.poetry.core.constraints.generic.any_constraint import AnyConstraint +from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint +from conda_lock._vendor.poetry.core.constraints.generic.empty_constraint import EmptyConstraint class Constraint(BaseConstraint): - OP_EQ = operator.eq OP_NE = operator.ne @@ -21,7 +15,7 @@ class Constraint(BaseConstraint): _trans_op_int = {OP_EQ: "==", OP_NE: "!="} - def __init__(self, version, operator="=="): # type: (str, str) -> None + def __init__(self, version: str, operator: str = "==") -> None: if operator == "=": operator = "==" @@ -30,14 +24,17 @@ def __init__(self, version, operator="=="): # type: (str, str) -> None self._op = self._trans_op_str[operator] @property - def version(self): # type: () -> str + def version(self) -> str: return self._version @property - def operator(self): # type: () -> str + def operator(self) -> str: return self._operator - def allows(self, other): # type: ("ConstraintTypes") -> bool + def allows(self, other: BaseConstraint) -> bool: + if not isinstance(other, Constraint): + raise ValueError("Unimplemented comparison of constraints") + is_equal_op = self._operator == "==" is_non_equal_op = self._operator == "!=" is_other_equal_op = other.operator == "==" @@ -58,13 +55,13 @@ def allows(self, other): # type: ("ConstraintTypes") -> bool return False - def allows_all(self, other): # type: ("ConstraintTypes") -> bool + def allows_all(self, other: BaseConstraint) -> bool: if not isinstance(other, Constraint): return other.is_empty() return other == self - def allows_any(self, other): # type: ("ConstraintTypes") -> bool + def allows_any(self, other: BaseConstraint) -> bool: if isinstance(other, Constraint): is_non_equal_op = self._operator == "!=" is_other_non_equal_op = other.operator == "!=" @@ -74,16 +71,14 @@ def allows_any(self, other): # type: ("ConstraintTypes") -> bool return other.allows(self) - def difference( - self, other - ): # type: ("ConstraintTypes") -> Union[Constraint, "EmptyConstraint"] + def difference(self, other: BaseConstraint) -> Constraint | EmptyConstraint: if other.allows(self): return EmptyConstraint() return self - def intersect(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - from .multi_constraint import MultiConstraint + def intersect(self, other: BaseConstraint) -> BaseConstraint: + from conda_lock._vendor.poetry.core.constraints.generic.multi_constraint import MultiConstraint if isinstance(other, Constraint): if other == self: @@ -102,30 +97,41 @@ def intersect(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" return other.intersect(self) - def union(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" + def union(self, other: BaseConstraint) -> BaseConstraint: if isinstance(other, Constraint): - from .union_constraint import UnionConstraint + from conda_lock._vendor.poetry.core.constraints.generic.union_constraint import UnionConstraint + + if other == self: + return self + + if self.operator == "!=" and other.operator == "==" and self.allows(other): + return self + + if other.operator == "!=" and self.operator == "==" and other.allows(self): + return other + + if other.operator == "==" and self.operator == "==": + return UnionConstraint(self, other) - return UnionConstraint(self, other) + return AnyConstraint() return other.union(self) - def is_any(self): # type: () -> bool + def is_any(self) -> bool: return False - def is_empty(self): # type: () -> bool + def is_empty(self) -> bool: return False - def __eq__(self, other): # type: (Any) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, Constraint): return NotImplemented return (self.version, self.operator) == (other.version, other.operator) - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash((self._operator, self._version)) - def __str__(self): # type: () -> str - return "{}{}".format( - self._operator if self._operator != "==" else "", self._version - ) + def __str__(self) -> str: + op = self._operator if self._operator != "==" else "" + return f"{op}{self._version}" diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/empty_constraint.py b/conda_lock/_vendor/poetry/core/constraints/generic/empty_constraint.py new file mode 100644 index 000000000..365e4e82a --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/empty_constraint.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint + + +class EmptyConstraint(BaseConstraint): + pretty_string = None + + def matches(self, _: BaseConstraint) -> bool: + return True + + def is_empty(self) -> bool: + return True + + def allows(self, other: BaseConstraint) -> bool: + return False + + def allows_all(self, other: BaseConstraint) -> bool: + return other.is_empty() + + def allows_any(self, other: BaseConstraint) -> bool: + return False + + def intersect(self, other: BaseConstraint) -> BaseConstraint: + return self + + def difference(self, other: BaseConstraint) -> BaseConstraint: + return self + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BaseConstraint): + return False + + return other.is_empty() + + def __hash__(self) -> int: + return hash("empty") + + def __str__(self) -> str: + return "" diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/multi_constraint.py b/conda_lock/_vendor/poetry/core/constraints/generic/multi_constraint.py new file mode 100644 index 000000000..f16319802 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/multi_constraint.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint +from conda_lock._vendor.poetry.core.constraints.generic.constraint import Constraint + + +class MultiConstraint(BaseConstraint): + def __init__(self, *constraints: Constraint) -> None: + if any(c.operator == "==" for c in constraints): + raise ValueError( + "A multi-constraint can only be comprised of negative constraints" + ) + + self._constraints = constraints + + @property + def constraints(self) -> tuple[Constraint, ...]: + return self._constraints + + def allows(self, other: BaseConstraint) -> bool: + return all(constraint.allows(other) for constraint in self._constraints) + + def allows_all(self, other: BaseConstraint) -> bool: + if other.is_any(): + return False + + if other.is_empty(): + return True + + if not isinstance(other, MultiConstraint): + return self.allows(other) + + our_constraints = iter(self._constraints) + their_constraints = iter(other.constraints) + our_constraint = next(our_constraints, None) + their_constraint = next(their_constraints, None) + + while our_constraint and their_constraint: + if our_constraint.allows_all(their_constraint): + their_constraint = next(their_constraints, None) + else: + our_constraint = next(our_constraints, None) + + return their_constraint is None + + def allows_any(self, other: BaseConstraint) -> bool: + if other.is_any(): + return True + + if other.is_empty(): + return True + + if isinstance(other, Constraint): + return self.allows(other) + + if isinstance(other, MultiConstraint): + return any( + c1.allows(c2) for c1 in self.constraints for c2 in other.constraints + ) + + return False + + def intersect(self, other: BaseConstraint) -> BaseConstraint: + if not isinstance(other, Constraint): + raise ValueError("Unimplemented constraint intersection") + + constraints = self._constraints + if other not in constraints: + constraints += (other,) + else: + constraints = (other,) + + if len(constraints) == 1: + return constraints[0] + + return MultiConstraint(*constraints) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, MultiConstraint): + return False + + return set(self._constraints) == set(other._constraints) + + def __hash__(self) -> int: + h = hash("multi") + for constraint in self._constraints: + h ^= hash(constraint) + + return h + + def __str__(self) -> str: + constraints = [] + for constraint in self._constraints: + constraints.append(str(constraint)) + + return ", ".join(constraints) diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/parser.py b/conda_lock/_vendor/poetry/core/constraints/generic/parser.py new file mode 100644 index 000000000..abd5d6b5d --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/parser.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import re + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.generic.any_constraint import AnyConstraint +from conda_lock._vendor.poetry.core.constraints.generic.constraint import Constraint +from conda_lock._vendor.poetry.core.constraints.generic.union_constraint import UnionConstraint +from conda_lock._vendor.poetry.core.constraints.version.exceptions import ParseConstraintError + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint + + +BASIC_CONSTRAINT = re.compile(r"^(!?==?)?\s*([^\s]+?)\s*$") + + +def parse_constraint(constraints: str) -> BaseConstraint: + if constraints == "*": + return AnyConstraint() + + or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip()) + or_groups = [] + for constraints in or_constraints: + and_constraints = re.split( + r"(?< ,]) *(? 1: + for constraint in and_constraints: + constraint_objects.append(parse_single_constraint(constraint)) + else: + constraint_objects.append(parse_single_constraint(and_constraints[0])) + + if len(constraint_objects) == 1: + constraint = constraint_objects[0] + else: + constraint = constraint_objects[0] + for next_constraint in constraint_objects[1:]: + constraint = constraint.intersect(next_constraint) + + or_groups.append(constraint) + + if len(or_groups) == 1: + return or_groups[0] + else: + return UnionConstraint(*or_groups) + + +def parse_single_constraint(constraint: str) -> Constraint: + # Basic comparator + m = BASIC_CONSTRAINT.match(constraint) + if m: + op = m.group(1) + if op is None: + op = "==" + + version = m.group(2).strip() + + return Constraint(version, op) + + raise ParseConstraintError(f"Could not parse version constraint: {constraint}") diff --git a/conda_lock/_vendor/poetry/core/constraints/generic/union_constraint.py b/conda_lock/_vendor/poetry/core/constraints/generic/union_constraint.py new file mode 100644 index 000000000..66e2f89f9 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/generic/union_constraint.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.generic.base_constraint import BaseConstraint +from conda_lock._vendor.poetry.core.constraints.generic.constraint import Constraint +from conda_lock._vendor.poetry.core.constraints.generic.empty_constraint import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.generic.multi_constraint import MultiConstraint + + +class UnionConstraint(BaseConstraint): + def __init__(self, *constraints: BaseConstraint) -> None: + self._constraints = constraints + + @property + def constraints(self) -> tuple[BaseConstraint, ...]: + return self._constraints + + def allows( + self, + other: BaseConstraint, + ) -> bool: + return any(constraint.allows(other) for constraint in self._constraints) + + def allows_any(self, other: BaseConstraint) -> bool: + if other.is_empty(): + return False + + if other.is_any(): + return True + + if isinstance(other, (UnionConstraint, MultiConstraint)): + constraints = other.constraints + else: + constraints = (other,) + + return any( + our_constraint.allows_any(their_constraint) + for our_constraint in self._constraints + for their_constraint in constraints + ) + + def allows_all(self, other: BaseConstraint) -> bool: + if other.is_any(): + return False + + if other.is_empty(): + return True + + if isinstance(other, (UnionConstraint, MultiConstraint)): + constraints = other.constraints + else: + constraints = (other,) + + our_constraints = iter(self._constraints) + their_constraints = iter(constraints) + our_constraint = next(our_constraints, None) + their_constraint = next(their_constraints, None) + + while our_constraint and their_constraint: + if our_constraint.allows_all(their_constraint): + their_constraint = next(their_constraints, None) + else: + our_constraint = next(our_constraints, None) + + return their_constraint is None + + def intersect(self, other: BaseConstraint) -> BaseConstraint: + if other.is_any(): + return self + + if other.is_empty(): + return other + + if isinstance(other, Constraint): + if self.allows(other): + return other + + return EmptyConstraint() + + # Two remaining cases: an intersection with another union, or an intersection + # with a multi. + # + # In the first case: + # (A or B) and (C or D) => (A and C) or (A and D) or (B and C) or (B and D) + # + # In the second case: + # (A or B) and (C and D) => (A and C and D) or (B and C and D) + new_constraints = [] + if isinstance(other, UnionConstraint): + for our_constraint in self._constraints: + for their_constraint in other.constraints: + intersection = our_constraint.intersect(their_constraint) + + if ( + not intersection.is_empty() + and intersection not in new_constraints + ): + new_constraints.append(intersection) + + else: + assert isinstance(other, MultiConstraint) + + for our_constraint in self._constraints: + intersection = our_constraint + for their_constraint in other.constraints: + intersection = intersection.intersect(their_constraint) + + if not intersection.is_empty() and intersection not in new_constraints: + new_constraints.append(intersection) + + if not new_constraints: + return EmptyConstraint() + + if len(new_constraints) == 1: + return new_constraints[0] + + return UnionConstraint(*new_constraints) + + def union(self, other: BaseConstraint) -> UnionConstraint: + if not isinstance(other, Constraint): + raise ValueError("Unimplemented constraint union") + + constraints = self._constraints + if other not in self._constraints: + constraints += (other,) + + return UnionConstraint(*constraints) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UnionConstraint): + return False + + return set(self._constraints) == set(other._constraints) + + def __hash__(self) -> int: + h = hash("union") + for constraint in self._constraints: + h ^= hash(constraint) + + return h + + def __str__(self) -> str: + constraints = [] + for constraint in self._constraints: + constraints.append(str(constraint)) + + return " || ".join(constraints) diff --git a/conda_lock/_vendor/poetry/core/constraints/version/__init__.py b/conda_lock/_vendor/poetry/core/constraints/version/__init__.py new file mode 100644 index 000000000..d25e8a02d --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/__init__.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.version.empty_constraint import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.version.parser import parse_constraint +from conda_lock._vendor.poetry.core.constraints.version.util import constraint_regions +from conda_lock._vendor.poetry.core.constraints.version.version import Version +from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint +from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange +from conda_lock._vendor.poetry.core.constraints.version.version_range_constraint import ( + VersionRangeConstraint, +) +from conda_lock._vendor.poetry.core.constraints.version.version_union import VersionUnion + + +__all__ = [ + "EmptyConstraint", + "Version", + "VersionConstraint", + "VersionRange", + "VersionRangeConstraint", + "VersionUnion", + "constraint_regions", + "parse_constraint", +] diff --git a/conda_lock/_vendor/poetry/core/constraints/version/empty_constraint.py b/conda_lock/_vendor/poetry/core/constraints/version/empty_constraint.py new file mode 100644 index 000000000..d5fb7fbf1 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/empty_constraint.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + from conda_lock._vendor.poetry.core.constraints.version.version_range_constraint import ( + VersionRangeConstraint, + ) + + +class EmptyConstraint(VersionConstraint): + def is_empty(self) -> bool: + return True + + def is_any(self) -> bool: + return False + + def is_simple(self) -> bool: + return True + + def allows(self, version: Version) -> bool: + return False + + def allows_all(self, other: VersionConstraint) -> bool: + return other.is_empty() + + def allows_any(self, other: VersionConstraint) -> bool: + return False + + def intersect(self, other: VersionConstraint) -> EmptyConstraint: + return self + + def union(self, other: VersionConstraint) -> VersionConstraint: + return other + + def difference(self, other: VersionConstraint) -> EmptyConstraint: + return self + + def flatten(self) -> list[VersionRangeConstraint]: + return [] + + def __str__(self) -> str: + return "" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, VersionConstraint): + return False + + return other.is_empty() + + def __hash__(self) -> int: + return hash("empty") diff --git a/conda_lock/_vendor/poetry/core/constraints/version/exceptions.py b/conda_lock/_vendor/poetry/core/constraints/version/exceptions.py new file mode 100644 index 000000000..d06e56f7c --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/exceptions.py @@ -0,0 +1,5 @@ +from __future__ import annotations + + +class ParseConstraintError(ValueError): + pass diff --git a/conda_lock/_vendor/poetry/core/constraints/version/parser.py b/conda_lock/_vendor/poetry/core/constraints/version/parser.py new file mode 100644 index 000000000..443082c19 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/parser.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import re + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.exceptions import ParseConstraintError +from conda_lock._vendor.poetry.core.version.exceptions import InvalidVersion + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint + + +def parse_constraint(constraints: str) -> VersionConstraint: + if constraints == "*": + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + return VersionRange() + + or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip()) + or_groups = [] + for constraints in or_constraints: + # allow trailing commas for robustness (even though it may not be + # standard-compliant it seems to occur in some packages) + constraints = constraints.rstrip(",").rstrip() + and_constraints = re.split( + "(?< ,]) *(? 1: + for constraint in and_constraints: + constraint_objects.append(parse_single_constraint(constraint)) + else: + constraint_objects.append(parse_single_constraint(and_constraints[0])) + + if len(constraint_objects) == 1: + constraint = constraint_objects[0] + else: + constraint = constraint_objects[0] + for next_constraint in constraint_objects[1:]: + constraint = constraint.intersect(next_constraint) + + or_groups.append(constraint) + + if len(or_groups) == 1: + return or_groups[0] + else: + from conda_lock._vendor.poetry.core.constraints.version.version_union import VersionUnion + + return VersionUnion.of(*or_groups) + + +def parse_single_constraint(constraint: str) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version.patterns import BASIC_CONSTRAINT + from conda_lock._vendor.poetry.core.constraints.version.patterns import CARET_CONSTRAINT + from conda_lock._vendor.poetry.core.constraints.version.patterns import TILDE_CONSTRAINT + from conda_lock._vendor.poetry.core.constraints.version.patterns import TILDE_PEP440_CONSTRAINT + from conda_lock._vendor.poetry.core.constraints.version.patterns import X_CONSTRAINT + from conda_lock._vendor.poetry.core.constraints.version.version import Version + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + from conda_lock._vendor.poetry.core.constraints.version.version_union import VersionUnion + + m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint) + if m: + return VersionRange() + + # Tilde range + m = TILDE_CONSTRAINT.match(constraint) + if m: + try: + version = Version.parse(m.group("version")) + except InvalidVersion as e: + raise ParseConstraintError( + f"Could not parse version constraint: {constraint}" + ) from e + + high = version.stable.next_minor() + if version.release.precision == 1: + high = version.stable.next_major() + + return VersionRange(version, high, include_min=True) + + # PEP 440 Tilde range (~=) + m = TILDE_PEP440_CONSTRAINT.match(constraint) + if m: + try: + version = Version.parse(m.group("version")) + except InvalidVersion as e: + raise ParseConstraintError( + f"Could not parse version constraint: {constraint}" + ) from e + + if version.release.precision == 2: + high = version.stable.next_major() + else: + high = version.stable.next_minor() + + return VersionRange(version, high, include_min=True) + + # Caret range + m = CARET_CONSTRAINT.match(constraint) + if m: + try: + version = Version.parse(m.group("version")) + except InvalidVersion as e: + raise ParseConstraintError( + f"Could not parse version constraint: {constraint}" + ) from e + + return VersionRange(version, version.next_breaking(), include_min=True) + + # X Range + m = X_CONSTRAINT.match(constraint) + if m: + op = m.group("op") + major = int(m.group(2)) + minor = m.group(3) + + if minor is not None: + version = Version.from_parts(major, int(minor), 0) + result: VersionConstraint = VersionRange( + version, version.next_minor(), include_min=True + ) + else: + if major == 0: + result = VersionRange(max=Version.from_parts(1, 0, 0)) + else: + version = Version.from_parts(major, 0, 0) + + result = VersionRange(version, version.next_major(), include_min=True) + + if op == "!=": + result = VersionRange().difference(result) + + return result + + # Basic comparator + m = BASIC_CONSTRAINT.match(constraint) + if m: + op = m.group("op") + version_string = m.group("version") + + if version_string == "dev": + version_string = "0.0-dev" + + try: + version = Version.parse(version_string) + except InvalidVersion as e: + raise ParseConstraintError( + f"Could not parse version constraint: {constraint}" + ) from e + + if op == "<": + return VersionRange(max=version) + if op == "<=": + return VersionRange(max=version, include_max=True) + if op == ">": + return VersionRange(min=version) + if op == ">=": + return VersionRange(min=version, include_min=True) + if op == "!=": + return VersionUnion(VersionRange(max=version), VersionRange(min=version)) + return version + + raise ParseConstraintError(f"Could not parse version constraint: {constraint}") diff --git a/conda_lock/_vendor/poetry/core/constraints/version/patterns.py b/conda_lock/_vendor/poetry/core/constraints/version/patterns.py new file mode 100644 index 000000000..0dd213cf3 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/patterns.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import re + +from packaging.version import VERSION_PATTERN + + +COMPLETE_VERSION = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE) + +CARET_CONSTRAINT = re.compile( + rf"^\^(?P{VERSION_PATTERN})$", re.VERBOSE | re.IGNORECASE +) +TILDE_CONSTRAINT = re.compile( + rf"^~(?!=)\s*(?P{VERSION_PATTERN})$", re.VERBOSE | re.IGNORECASE +) +TILDE_PEP440_CONSTRAINT = re.compile( + rf"^~=\s*(?P{VERSION_PATTERN})$", re.VERBOSE | re.IGNORECASE +) +X_CONSTRAINT = re.compile( + r"^(?P!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$" +) + +# note that we also allow technically incorrect version patterns with astrix (eg: 3.5.*) +# as this is supported by pip and appears in metadata within python packages +BASIC_CONSTRAINT = re.compile( + rf"^(?P<>|!=|>=?|<=?|==?)?\s*(?P{VERSION_PATTERN}|dev)(\.\*)?$", + re.VERBOSE | re.IGNORECASE, +) diff --git a/conda_lock/_vendor/poetry/core/constraints/version/util.py b/conda_lock/_vendor/poetry/core/constraints/version/util.py new file mode 100644 index 000000000..9a16cf1c8 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/util.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint + + +def constraint_regions(constraints: list[VersionConstraint]) -> list[VersionRange]: + """ + Transform a list of VersionConstraints into a list of VersionRanges that mark out + the distinct regions of version-space. + + eg input >=3.6 and >=2.7,<3.0.0 || >=3.4.0 + output <2.7, >=2.7,<3.0.0, >=3.0.0,<3.4.0, >=3.4.0,<3.6, >=3.6. + """ + flattened = [] + for constraint in constraints: + flattened += constraint.flatten() + + mins = { + (constraint.min, not constraint.include_min) + for constraint in flattened + if constraint.min is not None + } + maxs = { + (constraint.max, constraint.include_max) + for constraint in flattened + if constraint.max is not None + } + + edges = sorted(mins | maxs) + if not edges: + return [VersionRange(None, None)] + + start = edges[0] + regions = [ + VersionRange(None, start[0], include_max=start[1]), + ] + + for low, high in zip(edges, edges[1:]): + version_range = VersionRange( + low[0], + high[0], + include_min=not low[1], + include_max=high[1], + ) + regions.append(version_range) + + end = edges[-1] + regions.append( + VersionRange(end[0], None, include_min=not end[1]), + ) + + return regions diff --git a/conda_lock/_vendor/poetry/core/constraints/version/version.py b/conda_lock/_vendor/poetry/core/constraints/version/version.py new file mode 100644 index 000000000..0fc626b2a --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/version.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import dataclasses + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.empty_constraint import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.version.version_range_constraint import ( + VersionRangeConstraint, +) +from conda_lock._vendor.poetry.core.constraints.version.version_union import VersionUnion +from conda_lock._vendor.poetry.core.version.pep440 import Release +from conda_lock._vendor.poetry.core.version.pep440.version import PEP440Version + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint + from conda_lock._vendor.poetry.core.version.pep440 import LocalSegmentType + from conda_lock._vendor.poetry.core.version.pep440 import ReleaseTag + + +@dataclasses.dataclass(frozen=True) +class Version(PEP440Version, VersionRangeConstraint): + """ + A version constraint representing a single version. + """ + + @property + def precision(self) -> int: + return self.release.precision + + @property + def stable(self) -> Version: + if self.is_stable(): + return self + + post = self.post if self.pre is None else None + return Version(release=self.release, post=post, epoch=self.epoch) + + def next_breaking(self) -> Version: + if self.major > 0 or self.minor is None: + return self.stable.next_major() + + if self.minor > 0 or self.patch is None: + return self.stable.next_minor() + + return self.stable.next_patch() + + @property + def min(self) -> Version: + return self + + @property + def max(self) -> Version: + return self + + @property + def full_max(self) -> Version: + return self + + @property + def include_min(self) -> bool: + return True + + @property + def include_max(self) -> bool: + return True + + def is_any(self) -> bool: + return False + + def is_empty(self) -> bool: + return False + + def is_simple(self) -> bool: + return True + + def allows(self, version: Version | None) -> bool: + if version is None: + return False + + _this, _other = self, version + + # allow weak equality to allow `3.0.0+local.1` for `3.0.0` + if not _this.is_local() and _other.is_local(): + _other = _other.without_local() + + return _this == _other + + def allows_all(self, other: VersionConstraint) -> bool: + return other.is_empty() or ( + self.allows(other) if isinstance(other, self.__class__) else other == self + ) + + def allows_any(self, other: VersionConstraint) -> bool: + if isinstance(other, Version): + return self.allows(other) + + return other.allows(self) + + def intersect(self, other: VersionConstraint) -> Version | EmptyConstraint: + if other.allows(self): + return self + + if isinstance(other, Version) and self.allows(other): + return other + + return EmptyConstraint() + + def union(self, other: VersionConstraint) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + if other.allows(self): + return other + + if isinstance(other, VersionRangeConstraint): + if self.allows(other.min): + return VersionRange( + other.min, + other.max, + include_min=True, + include_max=other.include_max, + ) + + if self.allows(other.max): + return VersionRange( + other.min, + other.max, + include_min=other.include_min, + include_max=True, + ) + + return VersionUnion.of(self, other) + + def difference(self, other: VersionConstraint) -> Version | EmptyConstraint: + if other.allows(self): + return EmptyConstraint() + + return self + + def flatten(self) -> list[VersionRangeConstraint]: + return [self] + + def __str__(self) -> str: + return self.text + + def __eq__(self, other: object) -> bool: + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + if isinstance(other, VersionRange): + return ( + self == other.min + and self == other.max + and (other.include_min or other.include_max) + ) + return super().__eq__(other) + + @classmethod + def from_parts( + cls, + major: int, + minor: int | None = None, + patch: int | None = None, + extra: int | tuple[int, ...] = (), + pre: ReleaseTag | None = None, + post: ReleaseTag | None = None, + dev: ReleaseTag | None = None, + local: LocalSegmentType = None, + *, + epoch: int = 0, + ) -> Version: + if isinstance(extra, int): + extra = (extra,) + return cls( + release=Release(major=major, minor=minor, patch=patch, extra=extra), + pre=pre, + post=post, + dev=dev, + local=local, + epoch=epoch, + ) diff --git a/conda_lock/_vendor/poetry/core/constraints/version/version_constraint.py b/conda_lock/_vendor/poetry/core/constraints/version/version_constraint.py new file mode 100644 index 000000000..c2acae52a --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/version_constraint.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + from conda_lock._vendor.poetry.core.constraints.version.version_range_constraint import ( + VersionRangeConstraint, + ) + + +class VersionConstraint: + @abstractmethod + def is_empty(self) -> bool: + raise NotImplementedError() + + @abstractmethod + def is_any(self) -> bool: + raise NotImplementedError() + + @abstractmethod + def is_simple(self) -> bool: + raise NotImplementedError() + + @abstractmethod + def allows(self, version: Version) -> bool: + raise NotImplementedError() + + @abstractmethod + def allows_all(self, other: VersionConstraint) -> bool: + raise NotImplementedError() + + @abstractmethod + def allows_any(self, other: VersionConstraint) -> bool: + raise NotImplementedError() + + @abstractmethod + def intersect(self, other: VersionConstraint) -> VersionConstraint: + raise NotImplementedError() + + @abstractmethod + def union(self, other: VersionConstraint) -> VersionConstraint: + raise NotImplementedError() + + @abstractmethod + def difference(self, other: VersionConstraint) -> VersionConstraint: + raise NotImplementedError() + + @abstractmethod + def flatten(self) -> list[VersionRangeConstraint]: + raise NotImplementedError() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {str(self)}>" + + def __str__(self) -> str: + raise NotImplementedError() + + def __hash__(self) -> int: + raise NotImplementedError() + + def __eq__(self, other: object) -> bool: + raise NotImplementedError() diff --git a/conda_lock/_vendor/poetry/core/constraints/version/version_range.py b/conda_lock/_vendor/poetry/core/constraints/version/version_range.py new file mode 100644 index 000000000..a3125bab1 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/version_range.py @@ -0,0 +1,426 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.empty_constraint import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.version.version_range_constraint import ( + VersionRangeConstraint, +) +from conda_lock._vendor.poetry.core.constraints.version.version_union import VersionUnion + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint + + +class VersionRange(VersionRangeConstraint): + def __init__( + self, + min: Version | None = None, + max: Version | None = None, + include_min: bool = False, + include_max: bool = False, + always_include_max_prerelease: bool = False, + ) -> None: + full_max = max + if ( + not always_include_max_prerelease + and not include_max + and full_max is not None + and full_max.is_stable() + and not full_max.is_postrelease() + and (min is None or min.is_stable() or min.release != full_max.release) + ): + full_max = full_max.first_prerelease() + + self._min = min + self._max = max + self._full_max = full_max + self._include_min = include_min + self._include_max = include_max + + @property + def min(self) -> Version | None: + return self._min + + @property + def max(self) -> Version | None: + return self._max + + @property + def full_max(self) -> Version | None: + return self._full_max + + @property + def include_min(self) -> bool: + return self._include_min + + @property + def include_max(self) -> bool: + return self._include_max + + def is_empty(self) -> bool: + return False + + def is_any(self) -> bool: + return self._min is None and self._max is None + + def is_simple(self) -> bool: + return self._min is None or self._max is None + + def allows(self, other: Version) -> bool: + if self._min is not None: + if other < self._min: + return False + + if not self._include_min and other == self._min: + return False + + if self.full_max is not None: + _this, _other = self.full_max, other + + if not _this.is_local() and _other.is_local(): + # allow weak equality to allow `3.0.0+local.1` for `<=3.0.0` + _other = _other.without_local() + + if not _this.is_postrelease() and _other.is_postrelease(): + # allow weak equality to allow `3.0.0-1` for `<=3.0.0` + _other = _other.without_postrelease() + + if _other > _this: + return False + + if not self._include_max and _other == _this: + return False + + return True + + def allows_all(self, other: VersionConstraint) -> bool: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + if other.is_empty(): + return True + + if isinstance(other, Version): + return self.allows(other) + + if isinstance(other, VersionUnion): + return all([self.allows_all(constraint) for constraint in other.ranges]) + + if isinstance(other, VersionRangeConstraint): + return not other.allows_lower(self) and not other.allows_higher(self) + + raise ValueError(f"Unknown VersionConstraint type {other}.") + + def allows_any(self, other: VersionConstraint) -> bool: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + if other.is_empty(): + return False + + if isinstance(other, Version): + return self.allows(other) + + if isinstance(other, VersionUnion): + return any([self.allows_any(constraint) for constraint in other.ranges]) + + if isinstance(other, VersionRangeConstraint): + return not other.is_strictly_lower(self) and not other.is_strictly_higher( + self + ) + + raise ValueError(f"Unknown VersionConstraint type {other}.") + + def intersect(self, other: VersionConstraint) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + if other.is_empty(): + return other + + if isinstance(other, VersionUnion): + return other.intersect(self) + + # A range and a Version just yields the version if it's in the range. + if isinstance(other, Version): + if self.allows(other): + return other + + return EmptyConstraint() + + if not isinstance(other, VersionRangeConstraint): + raise ValueError(f"Unknown VersionConstraint type {other}.") + + if self.allows_lower(other): + if self.is_strictly_lower(other): + return EmptyConstraint() + + intersect_min = other.min + intersect_include_min = other.include_min + else: + if other.is_strictly_lower(self): + return EmptyConstraint() + + intersect_min = self._min + intersect_include_min = self._include_min + + if self.allows_higher(other): + intersect_max = other.max + intersect_include_max = other.include_max + else: + intersect_max = self._max + intersect_include_max = self._include_max + + if intersect_min is None and intersect_max is None: + return VersionRange() + + # If the range is just a single version. + if intersect_min == intersect_max: + # Because we already verified that the lower range isn't strictly + # lower, there must be some overlap. + assert intersect_include_min and intersect_include_max + assert intersect_min is not None + + return intersect_min + + # If we got here, there is an actual range. + return VersionRange( + intersect_min, intersect_max, intersect_include_min, intersect_include_max + ) + + def union(self, other: VersionConstraint) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + if isinstance(other, Version): + if self.allows(other): + return self + + if other == self.min: + return VersionRange( + self.min, self.max, include_min=True, include_max=self.include_max + ) + + if other == self.max: + return VersionRange( + self.min, self.max, include_min=self.include_min, include_max=True + ) + + return VersionUnion.of(self, other) + + if isinstance(other, VersionRangeConstraint): + # If the two ranges don't overlap, we won't be able to create a single + # VersionRange for both of them. + edges_touch = ( + self.max == other.min and (self.include_max or other.include_min) + ) or (self.min == other.max and (self.include_min or other.include_max)) + + if not edges_touch and not self.allows_any(other): + return VersionUnion.of(self, other) + + if self.allows_lower(other): + union_min = self.min + union_include_min = self.include_min + else: + union_min = other.min + union_include_min = other.include_min + + if self.allows_higher(other): + union_max = self.max + union_include_max = self.include_max + else: + union_max = other.max + union_include_max = other.include_max + + return VersionRange( + union_min, + union_max, + include_min=union_include_min, + include_max=union_include_max, + ) + + return VersionUnion.of(self, other) + + def difference(self, other: VersionConstraint) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + if other.is_empty(): + return self + + if isinstance(other, Version): + if not self.allows(other): + return self + + if other == self.min: + if not self.include_min: + return self + + return VersionRange(self.min, self.max, False, self.include_max) + + if other == self.max: + if not self.include_max: + return self + + return VersionRange(self.min, self.max, self.include_min, False) + + return VersionUnion.of( + VersionRange(self.min, other, self.include_min, False), + VersionRange(other, self.max, False, self.include_max), + ) + elif isinstance(other, VersionRangeConstraint): + if not self.allows_any(other): + return self + + before: VersionConstraint | None + if not self.allows_lower(other): + before = None + elif self.min == other.min: + before = self.min + else: + before = VersionRange( + self.min, other.min, self.include_min, not other.include_min + ) + + after: VersionConstraint | None + if not self.allows_higher(other): + after = None + elif self.max == other.max: + after = self.max + else: + after = VersionRange( + other.max, self.max, not other.include_max, self.include_max + ) + + if before is None and after is None: + return EmptyConstraint() + + if before is None: + assert after is not None + return after + + if after is None: + return before + + return VersionUnion.of(before, after) + elif isinstance(other, VersionUnion): + ranges: list[VersionRangeConstraint] = [] + current: VersionRangeConstraint = self + + for range in other.ranges: + # Skip any ranges that are strictly lower than [current]. + if range.is_strictly_lower(current): + continue + + # If we reach a range strictly higher than [current], no more ranges + # will be relevant so we can bail early. + if range.is_strictly_higher(current): + break + + difference = current.difference(range) + if difference.is_empty(): + return EmptyConstraint() + elif isinstance(difference, VersionUnion): + # If [range] split [current] in half, we only need to continue + # checking future ranges against the latter half. + ranges.append(difference.ranges[0]) + current = difference.ranges[-1] + else: + assert isinstance(difference, VersionRangeConstraint) + current = difference + + if not ranges: + return current + + return VersionUnion.of(*(ranges + [current])) + + raise ValueError(f"Unknown VersionConstraint type {other}.") + + def flatten(self) -> list[VersionRangeConstraint]: + return [self] + + def __eq__(self, other: object) -> bool: + if not isinstance(other, VersionRangeConstraint): + return False + + return ( + self._min == other.min + and self._max == other.max + and self._include_min == other.include_min + and self._include_max == other.include_max + ) + + def __lt__(self, other: VersionRangeConstraint) -> bool: + return self._cmp(other) < 0 + + def __le__(self, other: VersionRangeConstraint) -> bool: + return self._cmp(other) <= 0 + + def __gt__(self, other: VersionRangeConstraint) -> bool: + return self._cmp(other) > 0 + + def __ge__(self, other: VersionRangeConstraint) -> bool: + return self._cmp(other) >= 0 + + def _cmp(self, other: VersionRangeConstraint) -> int: + if self.min is None: + if other.min is None: + return self._compare_max(other) + + return -1 + elif other.min is None: + return 1 + + if self.min > other.min: + return 1 + elif self.min < other.min: + return -1 + + if self.include_min != other.include_min: + return -1 if self.include_min else 1 + + return self._compare_max(other) + + def _compare_max(self, other: VersionRangeConstraint) -> int: + if self.max is None: + if other.max is None: + return 0 + + return 1 + elif other.max is None: + return -1 + + if self.max > other.max: + return 1 + elif self.max < other.max: + return -1 + + if self.include_max != other.include_max: + return 1 if self.include_max else -1 + + return 0 + + def __str__(self) -> str: + text = "" + + if self.min is not None: + text += ">=" if self.include_min else ">" + text += self.min.text + + if self.max is not None: + if self.min is not None: + text += "," + + op = "<=" if self.include_max else "<" + text += f"{op}{self.max.text}" + + if self.min is None and self.max is None: + return "*" + + return text + + def __hash__(self) -> int: + return ( + hash(self.min) + ^ hash(self.max) + ^ hash(self.include_min) + ^ hash(self.include_max) + ) diff --git a/conda_lock/_vendor/poetry/core/constraints/version/version_range_constraint.py b/conda_lock/_vendor/poetry/core/constraints/version/version_range_constraint.py new file mode 100644 index 000000000..68149e5a9 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/version_range_constraint.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + +class VersionRangeConstraint(VersionConstraint): + @property + @abstractmethod + def min(self) -> Version | None: + raise NotImplementedError() + + @property + @abstractmethod + def max(self) -> Version | None: + raise NotImplementedError() + + @property + @abstractmethod + def full_max(self) -> Version | None: + raise NotImplementedError() + + @property + @abstractmethod + def include_min(self) -> bool: + raise NotImplementedError() + + @property + @abstractmethod + def include_max(self) -> bool: + raise NotImplementedError() + + def allows_lower(self, other: VersionRangeConstraint) -> bool: + if self.min is None: + return other.min is not None + + if other.min is None: + return False + + if self.min < other.min: + return True + + if self.min > other.min: + return False + + return self.include_min and not other.include_min + + def allows_higher(self, other: VersionRangeConstraint) -> bool: + if self.full_max is None: + return other.max is not None + + if other.full_max is None: + return False + + if self.full_max < other.full_max: + return False + + if self.full_max > other.full_max: + return True + + return self.include_max and not other.include_max + + def is_strictly_lower(self, other: VersionRangeConstraint) -> bool: + if self.full_max is None or other.min is None: + return False + + if self.full_max < other.min: + return True + + if self.full_max > other.min: + return False + + return not self.include_max or not other.include_min + + def is_strictly_higher(self, other: VersionRangeConstraint) -> bool: + return other.is_strictly_lower(self) + + def is_adjacent_to(self, other: VersionRangeConstraint) -> bool: + if self.max != other.min: + return False + + return ( + self.include_max + and not other.include_min + or not self.include_max + and other.include_min + ) diff --git a/conda_lock/_vendor/poetry/core/constraints/version/version_union.py b/conda_lock/_vendor/poetry/core/constraints/version/version_union.py new file mode 100644 index 000000000..69126e4ff --- /dev/null +++ b/conda_lock/_vendor/poetry/core/constraints/version/version_union.py @@ -0,0 +1,422 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from conda_lock._vendor.poetry.core.constraints.version.empty_constraint import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.version.version_constraint import VersionConstraint +from conda_lock._vendor.poetry.core.constraints.version.version_range_constraint import ( + VersionRangeConstraint, +) + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + + +class VersionUnion(VersionConstraint): + """ + A version constraint representing a union of multiple disjoint version + ranges. + + An instance of this will only be created if the version can't be represented + as a non-compound value. + """ + + def __init__(self, *ranges: VersionRangeConstraint) -> None: + self._ranges = list(ranges) + + @property + def ranges(self) -> list[VersionRangeConstraint]: + return self._ranges + + @classmethod + def of(cls, *ranges: VersionConstraint) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + flattened: list[VersionRangeConstraint] = [] + for constraint in ranges: + if constraint.is_empty(): + continue + + if isinstance(constraint, VersionUnion): + flattened += constraint.ranges + continue + + assert isinstance(constraint, VersionRangeConstraint) + flattened.append(constraint) + + if not flattened: + return EmptyConstraint() + + if any([constraint.is_any() for constraint in flattened]): + return VersionRange() + + # Only allow Versions and VersionRanges here so we can more easily reason + # about everything in flattened. _EmptyVersions and VersionUnions are + # filtered out above. + for constraint in flattened: + if not isinstance(constraint, VersionRangeConstraint): + raise ValueError(f"Unknown VersionConstraint type {constraint}.") + + flattened.sort() + + merged: list[VersionRangeConstraint] = [] + for constraint in flattened: + # Merge this constraint with the previous one, but only if they touch. + if not merged or ( + not merged[-1].allows_any(constraint) + and not merged[-1].is_adjacent_to(constraint) + ): + merged.append(constraint) + else: + new_constraint = merged[-1].union(constraint) + assert isinstance(new_constraint, VersionRangeConstraint) + merged[-1] = new_constraint + + if len(merged) == 1: + return merged[0] + + return VersionUnion(*merged) + + def is_empty(self) -> bool: + return False + + def is_any(self) -> bool: + return False + + def is_simple(self) -> bool: + return self.excludes_single_version() + + def allows(self, version: Version) -> bool: + return any([constraint.allows(version) for constraint in self._ranges]) + + def allows_all(self, other: VersionConstraint) -> bool: + our_ranges = iter(self._ranges) + their_ranges = iter(other.flatten()) + + our_current_range = next(our_ranges, None) + their_current_range = next(their_ranges, None) + + while our_current_range and their_current_range: + if our_current_range.allows_all(their_current_range): + their_current_range = next(their_ranges, None) + else: + our_current_range = next(our_ranges, None) + + return their_current_range is None + + def allows_any(self, other: VersionConstraint) -> bool: + our_ranges = iter(self._ranges) + their_ranges = iter(other.flatten()) + + our_current_range = next(our_ranges, None) + their_current_range = next(their_ranges, None) + + while our_current_range and their_current_range: + if our_current_range.allows_any(their_current_range): + return True + + if their_current_range.allows_higher(our_current_range): + our_current_range = next(our_ranges, None) + else: + their_current_range = next(their_ranges, None) + + return False + + def intersect(self, other: VersionConstraint) -> VersionConstraint: + our_ranges = iter(self._ranges) + their_ranges = iter(other.flatten()) + new_ranges = [] + + our_current_range = next(our_ranges, None) + their_current_range = next(their_ranges, None) + + while our_current_range and their_current_range: + intersection = our_current_range.intersect(their_current_range) + + if not intersection.is_empty(): + new_ranges.append(intersection) + + if their_current_range.allows_higher(our_current_range): + our_current_range = next(our_ranges, None) + else: + their_current_range = next(their_ranges, None) + + return VersionUnion.of(*new_ranges) + + def union(self, other: VersionConstraint) -> VersionConstraint: + return VersionUnion.of(self, other) + + def difference(self, other: VersionConstraint) -> VersionConstraint: + our_ranges = iter(self._ranges) + their_ranges = iter(other.flatten()) + new_ranges: list[VersionConstraint] = [] + + state = { + "current": next(our_ranges, None), + "their_range": next(their_ranges, None), + } + + def their_next_range() -> bool: + state["their_range"] = next(their_ranges, None) + if state["their_range"]: + return True + + assert state["current"] is not None + new_ranges.append(state["current"]) + our_current = next(our_ranges, None) + while our_current: + new_ranges.append(our_current) + our_current = next(our_ranges, None) + + return False + + def our_next_range(include_current: bool = True) -> bool: + if include_current: + assert state["current"] is not None + new_ranges.append(state["current"]) + + our_current = next(our_ranges, None) + if not our_current: + return False + + state["current"] = our_current + + return True + + while True: + if state["their_range"] is None: + break + + assert state["current"] is not None + if state["their_range"].is_strictly_lower(state["current"]): + if not their_next_range(): + break + + continue + + if state["their_range"].is_strictly_higher(state["current"]): + if not our_next_range(): + break + + continue + + difference = state["current"].difference(state["their_range"]) + if isinstance(difference, VersionUnion): + assert len(difference.ranges) == 2 + new_ranges.append(difference.ranges[0]) + state["current"] = difference.ranges[-1] + + if not their_next_range(): + break + elif difference.is_empty(): + if not our_next_range(False): + break + else: + assert isinstance(difference, VersionRangeConstraint) + state["current"] = difference + + if state["current"].allows_higher(state["their_range"]): + if not their_next_range(): + break + else: + if not our_next_range(): + break + + if not new_ranges: + return EmptyConstraint() + + if len(new_ranges) == 1: + return new_ranges[0] + + return VersionUnion.of(*new_ranges) + + def flatten(self) -> list[VersionRangeConstraint]: + return self.ranges + + def _exclude_single_wildcard_range_string(self) -> str: + """ + Helper method to convert this instance into a wild card range + string. + """ + if not self.excludes_single_wildcard_range(): + raise ValueError("Not a valid wildcard range") + + # we assume here that since it is a single exclusion range + # that it is one of "< 2.0.0 || >= 2.1.0" or ">= 2.1.0 || < 2.0.0" + # and the one with the max is the first part + idx_order = (0, 1) if self._ranges[0].max else (1, 0) + one = self._ranges[idx_order[0]].max + assert one is not None + two = self._ranges[idx_order[1]].min + assert two is not None + + # versions can have both semver and non semver parts + parts_one = [ + one.major, + one.minor or 0, + one.patch or 0, + *list(one.non_semver_parts or []), + ] + parts_two = [ + two.major, + two.minor or 0, + two.patch or 0, + *list(two.non_semver_parts or []), + ] + + # we assume here that a wildcard range implies that the part following the + # first part that is different in the second range is the wildcard, this means + # that multiple wildcards are not supported right now. + parts = [] + + for idx, part in enumerate(parts_one): + parts.append(str(part)) + if parts_two[idx] != part: + # since this part is different the next one is the wildcard + # for example, "< 2.0.0 || >= 2.1.0" gets us a wildcard range + # 2.0.* + parts.append("*") + break + else: + # we should not ever get here, however it is likely that poorly + # constructed metadata exists + raise ValueError("Not a valid wildcard range") + + return f"!={'.'.join(parts)}" + + @staticmethod + def _excludes_single_wildcard_range_check_is_valid_range( + one: VersionRangeConstraint, two: VersionRangeConstraint + ) -> bool: + """ + Helper method to determine if two versions define a single wildcard range. + + In cases where !=2.0.* was parsed by us, the union is of the range + <2.0.0 || >=2.1.0. In user defined ranges, precision might be different. + For example, a union <2.0 || >= 2.1.0 is still !=2.0.*. In order to + handle these cases we make sure that if precisions do not match, extra + checks are performed to validate that the constraint is a valid single + wildcard range. + """ + + assert one.max is not None + assert two.min is not None + + max_precision = max(one.max.precision, two.min.precision) + + if max_precision <= 3: + # In cases where both versions have a precision less than 3, + # we can make use of the next major/minor/patch versions. + return two.min in { + one.max.next_major(), + one.max.next_minor(), + one.max.next_patch(), + } + else: + # When there are non-semver parts in one of the versions, we need to + # ensure we use zero padded version and in addition to next major/minor/ + # patch versions, also check each next release for the extra parts. + from_parts = one.max.__class__.from_parts + + _extras: list[list[int]] = [] + _versions: list[Version] = [] + + for _version in [one.max, two.min]: + _extra = list(_version.non_semver_parts or []) + + while len(_extra) < (max_precision - 3): + # pad zeros for extra parts to ensure precisions are equal + _extra.append(0) + + # create a new release with unspecified parts padded with zeros + _padded_version: Version = from_parts( + major=_version.major, + minor=_version.minor or 0, + patch=_version.patch or 0, + extra=tuple(_extra), + ) + + _extras.append(_extra) + _versions.append(_padded_version) + + _extra_one = _extras[0] + _padded_version_one = _versions[0] + _padded_version_two = _versions[1] + + _check_versions = { + _padded_version_one.next_major(), + _padded_version_one.next_minor(), + _padded_version_one.next_patch(), + } + + # for each non-semver (extra) part, bump a version + for idx in range(len(_extra_one)): + _extra = [ + *_extra_one[: idx - 1], + (_extra_one[idx] + 1), + *_extra_one[idx + 1 :], + ] + _check_versions.add( + from_parts( + _padded_version_one.major, + _padded_version_one.minor, + _padded_version_one.patch, + tuple(_extra), + ) + ) + + return _padded_version_two in _check_versions + + def excludes_single_wildcard_range(self) -> bool: + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + if len(self._ranges) != 2: + return False + + idx_order = (0, 1) if self._ranges[0].max else (1, 0) + one = self._ranges[idx_order[0]] + two = self._ranges[idx_order[1]] + + is_range_exclusion = ( + one.max and not one.include_max and two.min and two.include_min + ) + + if not is_range_exclusion: + return False + + if not self._excludes_single_wildcard_range_check_is_valid_range(one, two): + return False + + return isinstance(VersionRange().difference(self), VersionRange) + + def excludes_single_version(self) -> bool: + from conda_lock._vendor.poetry.core.constraints.version.version import Version + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + return isinstance(VersionRange().difference(self), Version) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, VersionUnion): + return False + + return self._ranges == other.ranges + + def __hash__(self) -> int: + h = hash(self._ranges[0]) + + for range in self._ranges[1:]: + h ^= hash(range) + + return h + + def __str__(self) -> str: + from conda_lock._vendor.poetry.core.constraints.version.version_range import VersionRange + + if self.excludes_single_version(): + return f"!={VersionRange().difference(self)}" + + try: + return self._exclude_single_wildcard_range_string() + except ValueError: + return " || ".join([str(r) for r in self._ranges]) diff --git a/conda_lock/_vendor/poetry/core/exceptions/__init__.py b/conda_lock/_vendor/poetry/core/exceptions/__init__.py index d5ff9062a..6050698d3 100644 --- a/conda_lock/_vendor/poetry/core/exceptions/__init__.py +++ b/conda_lock/_vendor/poetry/core/exceptions/__init__.py @@ -1,4 +1,6 @@ +from __future__ import annotations + from conda_lock._vendor.poetry.core.exceptions.base import PoetryCoreException -__all__ = [clazz.__name__ for clazz in {PoetryCoreException}] +__all__ = ["PoetryCoreException"] diff --git a/conda_lock/_vendor/poetry/core/exceptions/base.py b/conda_lock/_vendor/poetry/core/exceptions/base.py index 41b1c3e8a..437276284 100644 --- a/conda_lock/_vendor/poetry/core/exceptions/base.py +++ b/conda_lock/_vendor/poetry/core/exceptions/base.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + class PoetryCoreException(Exception): pass diff --git a/conda_lock/_vendor/poetry/core/factory.py b/conda_lock/_vendor/poetry/core/factory.py index be157f00a..924b14ec7 100644 --- a/conda_lock/_vendor/poetry/core/factory.py +++ b/conda_lock/_vendor/poetry/core/factory.py @@ -1,35 +1,49 @@ -from __future__ import absolute_import -from __future__ import unicode_literals +from __future__ import annotations import logging +from pathlib import Path +from typing import TYPE_CHECKING from typing import Any from typing import Dict from typing import List -from typing import Optional +from typing import Mapping from typing import Union from warnings import warn -from .json import validate_object -from .packages.dependency import Dependency -from .packages.project_package import ProjectPackage -from .poetry import Poetry -from .pyproject import PyProjectTOML -from .spdx import license_by_id -from .utils._compat import Path +from packaging.utils import canonicalize_name + +from conda_lock._vendor.poetry.core.utils.helpers import combine_unicode +from conda_lock._vendor.poetry.core.utils.helpers import readme_content_type + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.dependency_group import DependencyGroup + from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage + from conda_lock._vendor.poetry.core.poetry import Poetry + from conda_lock._vendor.poetry.core.spdx.license import License + + DependencyConstraint = Union[str, Dict[str, Any]] + DependencyConfig = Mapping[ + str, Union[List[DependencyConstraint], DependencyConstraint] + ] logger = logging.getLogger(__name__) -class Factory(object): +class Factory: """ Factory class to create various elements needed by Poetry. """ def create_poetry( - self, cwd=None, with_dev=True - ): # type: (Optional[Path], bool) -> Poetry + self, cwd: Path | None = None, with_groups: bool = True + ) -> Poetry: + from conda_lock._vendor.poetry.core.poetry import Poetry + from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML + poetry_file = self.locate(cwd) local_config = PyProjectTOML(path=poetry_file).poetry_config @@ -38,84 +52,133 @@ def create_poetry( if check_result["errors"]: message = "" for error in check_result["errors"]: - message += " - {}\n".format(error) + message += f" - {error}\n" raise RuntimeError("The Poetry configuration is invalid:\n" + message) # Load package name = local_config["name"] + assert isinstance(name, str) version = local_config["version"] - package = ProjectPackage(name, version, version) - package.root_dir = poetry_file.parent - - for author in local_config["authors"]: - package.authors.append(author) + assert isinstance(version, str) + package = self.get_package(name, version) + package = self.configure_package( + package, local_config, poetry_file.parent, with_groups=with_groups + ) - for maintainer in local_config.get("maintainers", []): - package.maintainers.append(maintainer) + return Poetry(poetry_file, local_config, package) - package.description = local_config.get("description", "") - package.homepage = local_config.get("homepage") - package.repository_url = local_config.get("repository") - package.documentation_url = local_config.get("documentation") - try: - license_ = license_by_id(local_config.get("license", "")) - except ValueError: - license_ = None + @classmethod + def get_package(cls, name: str, version: str) -> ProjectPackage: + from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage - package.license = license_ - package.keywords = local_config.get("keywords", []) - package.classifiers = local_config.get("classifiers", []) + return ProjectPackage(name, version, version) - if "readme" in local_config: - package.readme = Path(poetry_file.parent) / local_config["readme"] + @classmethod + def _add_package_group_dependencies( + cls, + package: ProjectPackage, + group: str | DependencyGroup, + dependencies: DependencyConfig, + ) -> None: + from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP + + if isinstance(group, str): + if package.has_dependency_group(group): + group = package.dependency_group(group) + else: + from conda_lock._vendor.poetry.core.packages.dependency_group import DependencyGroup - if "platform" in local_config: - package.platform = local_config["platform"] + group = DependencyGroup(group) - if "dependencies" in local_config: - for name, constraint in local_config["dependencies"].items(): + for name, constraints in dependencies.items(): + _constraints = ( + constraints if isinstance(constraints, list) else [constraints] + ) + for _constraint in _constraints: if name.lower() == "python": - package.python_versions = constraint + if group.name == MAIN_GROUP and isinstance(_constraint, str): + package.python_versions = _constraint continue - if isinstance(constraint, list): - for _constraint in constraint: - package.add_dependency( - self.create_dependency( - name, _constraint, root_dir=package.root_dir - ) - ) + group.add_dependency( + cls.create_dependency( + name, + _constraint, + groups=[group.name], + root_dir=package.root_dir, + ) + ) - continue + package.add_dependency_group(group) - package.add_dependency( - self.create_dependency(name, constraint, root_dir=package.root_dir) - ) + @classmethod + def configure_package( + cls, + package: ProjectPackage, + config: dict[str, Any], + root: Path, + with_groups: bool = True, + ) -> ProjectPackage: + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP + from conda_lock._vendor.poetry.core.packages.dependency_group import DependencyGroup + from conda_lock._vendor.poetry.core.spdx.helpers import license_by_id + + package.root_dir = root + + for author in config["authors"]: + package.authors.append(combine_unicode(author)) + + for maintainer in config.get("maintainers", []): + package.maintainers.append(combine_unicode(maintainer)) + + package.description = config.get("description", "") + package.homepage = config.get("homepage") + package.repository_url = config.get("repository") + package.documentation_url = config.get("documentation") + try: + license_: License | None = license_by_id(config.get("license", "")) + except ValueError: + license_ = None - if with_dev and "dev-dependencies" in local_config: - for name, constraint in local_config["dev-dependencies"].items(): - if isinstance(constraint, list): - for _constraint in constraint: - package.add_dependency( - self.create_dependency( - name, - _constraint, - category="dev", - root_dir=package.root_dir, - ) - ) + package.license = license_ + package.keywords = config.get("keywords", []) + package.classifiers = config.get("classifiers", []) - continue + if "readme" in config: + if isinstance(config["readme"], str): + package.readmes = (root / config["readme"],) + else: + package.readmes = tuple(root / readme for readme in config["readme"]) - package.add_dependency( - self.create_dependency( - name, constraint, category="dev", root_dir=package.root_dir - ) + if "platform" in config: + package.platform = config["platform"] + + if "dependencies" in config: + cls._add_package_group_dependencies( + package=package, group=MAIN_GROUP, dependencies=config["dependencies"] + ) + + if with_groups and "group" in config: + for group_name, group_config in config["group"].items(): + group = DependencyGroup( + group_name, optional=group_config.get("optional", False) ) + cls._add_package_group_dependencies( + package=package, + group=group, + dependencies=group_config["dependencies"], + ) + + if with_groups and "dev-dependencies" in config: + cls._add_package_group_dependencies( + package=package, group="dev", dependencies=config["dev-dependencies"] + ) - extras = local_config.get("extras", {}) + extras = config.get("extras", {}) for extra_name, requirements in extras.items(): + extra_name = canonicalize_name(extra_name) package.extras[extra_name] = [] # Checking for dependency @@ -127,18 +190,16 @@ def create_poetry( dep.in_extras.append(extra_name) package.extras[extra_name].append(dep) - break - - if "build" in local_config: - build = local_config["build"] + if "build" in config: + build = config["build"] if not isinstance(build, dict): build = {"script": build} package.build_config = build or {} - if "include" in local_config: + if "include" in config: package.include = [] - for include in local_config["include"]: + for include in config["include"]: if not isinstance(include, dict): include = {"path": include} @@ -149,34 +210,44 @@ def create_poetry( package.include.append(include) - if "exclude" in local_config: - package.exclude = local_config["exclude"] + if "exclude" in config: + package.exclude = config["exclude"] - if "packages" in local_config: - package.packages = local_config["packages"] + if "packages" in config: + package.packages = config["packages"] # Custom urls - if "urls" in local_config: - package.custom_urls = local_config["urls"] + if "urls" in config: + package.custom_urls = config["urls"] - return Poetry(poetry_file, local_config, package) + return package @classmethod def create_dependency( cls, - name, # type: str - constraint, # type: Union[str, Dict[str, Any]] - category="main", # type: str - root_dir=None, # type: Optional[Path] - ): # type: (...) -> Dependency - from .packages.constraints import parse_constraint as parse_generic_constraint - from .packages.directory_dependency import DirectoryDependency - from .packages.file_dependency import FileDependency - from .packages.url_dependency import URLDependency - from .packages.utils.utils import create_nested_marker - from .packages.vcs_dependency import VCSDependency - from .version.markers import AnyMarker - from .version.markers import parse_marker + name: str, + constraint: DependencyConstraint, + groups: list[str] | None = None, + root_dir: Path | None = None, + ) -> Dependency: + from conda_lock._vendor.poetry.core.constraints.generic import ( + parse_constraint as parse_generic_constraint, + ) + from conda_lock._vendor.poetry.core.constraints.version import ( + parse_constraint as parse_version_constraint, + ) + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP + from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency + from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency + from conda_lock._vendor.poetry.core.packages.url_dependency import URLDependency + from conda_lock._vendor.poetry.core.packages.utils.utils import create_nested_marker + from conda_lock._vendor.poetry.core.packages.vcs_dependency import VCSDependency + from conda_lock._vendor.poetry.core.version.markers import AnyMarker + from conda_lock._vendor.poetry.core.version.markers import parse_marker + + if groups is None: + groups = [MAIN_GROUP] if constraint is None: constraint = "*" @@ -188,9 +259,9 @@ def create_dependency( markers = constraint.get("markers") if "allows-prereleases" in constraint: message = ( - 'The "{}" dependency specifies ' + f'The "{name}" dependency specifies ' 'the "allows-prereleases" property, which is deprecated. ' - 'Use "allow-prereleases" instead.'.format(name) + 'Use "allow-prereleases" instead.' ) warn(message, DeprecationWarning) logger.warning(message) @@ -199,6 +270,7 @@ def create_dependency( "allow-prereleases", constraint.get("allows-prereleases", False) ) + dependency: Dependency if "git" in constraint: # VCS dependency dependency = VCSDependency( @@ -208,7 +280,8 @@ def create_dependency( branch=constraint.get("branch", None), tag=constraint.get("tag", None), rev=constraint.get("rev", None), - category=category, + directory=constraint.get("subdirectory", None), + groups=groups, optional=optional, develop=constraint.get("develop", False), extras=constraint.get("extras", []), @@ -219,7 +292,7 @@ def create_dependency( dependency = FileDependency( name, file_path, - category=category, + groups=groups, base=root_dir, extras=constraint.get("extras", []), ) @@ -235,7 +308,7 @@ def create_dependency( dependency = FileDependency( name, path, - category=category, + groups=groups, optional=optional, base=root_dir, extras=constraint.get("extras", []), @@ -244,7 +317,7 @@ def create_dependency( dependency = DirectoryDependency( name, path, - category=category, + groups=groups, optional=optional, base=root_dir, develop=constraint.get("develop", False), @@ -254,7 +327,8 @@ def create_dependency( dependency = URLDependency( name, constraint["url"], - category=category, + directory=constraint.get("subdirectory", None), + groups=groups, optional=optional, extras=constraint.get("extras", []), ) @@ -265,51 +339,50 @@ def create_dependency( name, version, optional=optional, - category=category, + groups=groups, allows_prereleases=allows_prereleases, extras=constraint.get("extras", []), ) - if not markers: - marker = AnyMarker() - if python_versions: - dependency.python_versions = python_versions - marker = marker.intersect( - parse_marker( - create_nested_marker( - "python_version", dependency.python_constraint - ) + marker = parse_marker(markers) if markers else AnyMarker() + + if python_versions: + marker = marker.intersect( + parse_marker( + create_nested_marker( + "python_version", parse_version_constraint(python_versions) ) ) + ) - if platform: - marker = marker.intersect( - parse_marker( - create_nested_marker( - "sys_platform", parse_generic_constraint(platform) - ) + if platform: + marker = marker.intersect( + parse_marker( + create_nested_marker( + "sys_platform", parse_generic_constraint(platform) ) ) - else: - marker = parse_marker(markers) + ) if not marker.is_any(): dependency.marker = marker dependency.source_name = constraint.get("source") else: - dependency = Dependency(name, constraint, category=category) + dependency = Dependency(name, constraint, groups=groups) return dependency @classmethod def validate( - cls, config, strict=False - ): # type: (dict, bool) -> Dict[str, List[str]] + cls, config: dict[str, Any], strict: bool = False + ) -> dict[str, list[str]]: """ Checks the validity of a configuration """ - result = {"errors": [], "warnings": []} + from conda_lock._vendor.poetry.core.json import validate_object + + result: dict[str, list[str]] = {"errors": [], "warnings": []} # Schema validation errors validation_errors = validate_object(config, "poetry-schema") @@ -331,33 +404,44 @@ def validate( if "allows-prereleases" in constraint: result["warnings"].append( - 'The "{}" dependency specifies ' + f'The "{name}" dependency specifies ' 'the "allows-prereleases" property, which is deprecated. ' - 'Use "allow-prereleases" instead.'.format(name) + 'Use "allow-prereleases" instead.' ) # Checking for scripts with extras if "scripts" in config: scripts = config["scripts"] + config_extras = config.get("extras", {}) + for name, script in scripts.items(): if not isinstance(script, dict): continue - extras = script["extras"] + extras = script.get("extras", []) for extra in extras: - if extra not in config["extras"]: + if extra not in config_extras: result["errors"].append( - 'Script "{}" requires extra "{}" which is not defined.'.format( - name, extra - ) + f'Script "{name}" requires extra "{extra}" which is not' + " defined." ) + # Checking types of all readme files (must match) + if "readme" in config and not isinstance(config["readme"], str): + readme_types = {readme_content_type(r) for r in config["readme"]} + if len(readme_types) > 1: + result["errors"].append( + "Declared README files must be of same type: found" + f" {', '.join(sorted(readme_types))}" + ) + return result @classmethod - def locate(cls, cwd): # type: (Path) -> Path - candidates = [Path(cwd)] - candidates.extend(Path(cwd).parents) + def locate(cls, cwd: Path | None = None) -> Path: + cwd = Path(cwd or Path.cwd()) + candidates = [cwd] + candidates.extend(cwd.parents) for path in candidates: poetry_file = path / "pyproject.toml" @@ -367,7 +451,5 @@ def locate(cls, cwd): # type: (Path) -> Path else: raise RuntimeError( - "Poetry could not find a pyproject.toml file in {} or its parents".format( - cwd - ) + f"Poetry could not find a pyproject.toml file in {cwd} or its parents" ) diff --git a/conda_lock/_vendor/poetry/core/json/__init__.py b/conda_lock/_vendor/poetry/core/json/__init__.py index 83ecab77c..c46a8d264 100644 --- a/conda_lock/_vendor/poetry/core/json/__init__.py +++ b/conda_lock/_vendor/poetry/core/json/__init__.py @@ -1,40 +1,39 @@ +from __future__ import annotations + import json import os -from io import open -from typing import List - -from jsonschema import Draft7Validator +from typing import Any SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas") class ValidationError(ValueError): - pass -def validate_object(obj, schema_name): # type: (dict, str) -> List[str] - schema = os.path.join(SCHEMA_DIR, "{}.json".format(schema_name)) +def validate_object(obj: dict[str, Any], schema_name: str) -> list[str]: + schema_file = os.path.join(SCHEMA_DIR, f"{schema_name}.json") - if not os.path.exists(schema): - raise ValueError("Schema {} does not exist.".format(schema_name)) + if not os.path.exists(schema_file): + raise ValueError(f"Schema {schema_name} does not exist.") - with open(schema, encoding="utf-8") as f: + with open(schema_file, encoding="utf-8") as f: schema = json.loads(f.read()) + from jsonschema import Draft7Validator + validator = Draft7Validator(schema) - validation_errors = sorted(validator.iter_errors(obj), key=lambda e: e.path) + validation_errors = sorted(validator.iter_errors(obj), key=lambda e: e.path) # type: ignore[no-any-return] errors = [] for error in validation_errors: message = error.message if error.path: - message = "[{}] {}".format( - ".".join(str(x) for x in error.absolute_path), message - ) + path = ".".join(str(x) for x in error.absolute_path) + message = f"[{path}] {message}" errors.append(message) diff --git a/conda_lock/_vendor/poetry/core/json/schemas/poetry-schema.json b/conda_lock/_vendor/poetry/core/json/schemas/poetry-schema.json index 81664910f..8ff976f5b 100644 --- a/conda_lock/_vendor/poetry/core/json/schemas/poetry-schema.json +++ b/conda_lock/_vendor/poetry/core/json/schemas/poetry-schema.json @@ -1,591 +1,655 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "name": "Package", - "type": "object", - "additionalProperties": false, - "required": [ - "name", - "version", - "description" - ], - "properties": { - "name": { - "type": "string", - "description": "Package name." - }, - "version": { - "type": "string", - "description": "Package version." - }, - "description": { - "type": "string", - "description": "Short package description." - }, - "keywords": { - "type": "array", - "items": { - "type": "string", - "description": "A tag/keyword that this package relates to." - } - }, - "homepage": { - "type": "string", - "description": "Homepage URL for the project.", - "format": "uri" - }, - "repository": { - "type": "string", - "description": "Repository URL for the project.", - "format": "uri" - }, - "documentation": { - "type": "string", - "description": "Documentation URL for the project.", - "format": "uri" - }, - "license": { - "type": "string", - "description": "License name." - }, - "authors": { - "$ref": "#/definitions/authors" - }, - "maintainers": { - "$ref": "#/definitions/maintainers" - }, - "readme": { + "$schema": "http://json-schema.org/draft-04/schema#", + "name": "Package", + "type": "object", + "additionalProperties": true, + "required": [ + "name", + "version", + "description", + "authors" + ], + "properties": { + "name": { + "type": "string", + "description": "Package name." + }, + "version": { + "type": "string", + "description": "Package version." + }, + "description": { + "type": "string", + "description": "Short package description.", + "pattern": "^[^\n]*$" + }, + "keywords": { + "type": "array", + "items": { + "type": "string", + "description": "A tag/keyword that this package relates to." + } + }, + "homepage": { + "type": "string", + "description": "Homepage URL for the project.", + "format": "uri" + }, + "repository": { + "type": "string", + "description": "Repository URL for the project.", + "format": "uri" + }, + "documentation": { + "type": "string", + "description": "Documentation URL for the project.", + "format": "uri" + }, + "license": { + "type": "string", + "description": "License name." + }, + "authors": { + "$ref": "#/definitions/authors" + }, + "maintainers": { + "$ref": "#/definitions/maintainers" + }, + "readme": { + "anyOf": [ + { + "type": "string", + "description": "The path to the README file." + }, + { + "type": "array", + "description": "A list of paths to the readme files.", + "items": { + "type": "string" + } + } + ] + }, + "classifiers": { + "type": "array", + "description": "A list of trove classifiers." + }, + "packages": { + "type": "array", + "description": "A list of packages to include in the final distribution.", + "items": { + "type": "object", + "description": "Information about where the package resides.", + "additionalProperties": false, + "required": [ + "include" + ], + "properties": { + "include": { + "$ref": "#/definitions/include-path" + }, + "from": { "type": "string", - "description": "The path to the README file" - }, - "classifiers": { - "type": "array", - "description": "A list of trove classifers." - }, - "packages": { - "type": "array", - "description": "A list of packages to include in the final distribution.", - "items": { - "type": "object", - "description": "Information about where the package resides.", - "additionalProperties": false, - "required": [ - "include" - ], - "properties": { - "include": { - "$ref": "#/definitions/include-path" - }, - "from": { - "type": "string", - "description": "Where the source directory of the package resides." - }, - "format": { - "$ref": "#/definitions/package-formats" - } - } - } - }, - "include": { - "type": "array", - "description": "A list of files and folders to include.", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/include-path" - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "path" - ], - "properties": { - "path": { - "$ref": "#/definitions/include-path" - }, - "format": { - "$ref": "#/definitions/package-formats" - } - } - } - ] - } - }, - "exclude": { - "type": "array", - "description": "A list of files and folders to exclude." - }, - "dependencies": { + "description": "Where the source directory of the package resides." + }, + "format": { + "$ref": "#/definitions/package-formats" + } + } + } + }, + "include": { + "type": "array", + "description": "A list of files and folders to include.", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/include-path" + }, + { "type": "object", - "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.", + "additionalProperties": false, "required": [ - "python" + "path" ], "properties": { - "python": { - "type": "string", - "description": "The Python versions the package is compatible with." - } - }, - "$ref": "#/definitions/dependencies", - "additionalProperties": false - }, - "dev-dependencies": { - "type": "object", - "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", - "$ref": "#/definitions/dependencies", - "additionalProperties": false - }, - "extras": { - "type": "object", - "patternProperties": { - "^[a-zA-Z-_.0-9]+$": { - "type": "array", - "items": { - "type": "string" - } - } + "path": { + "$ref": "#/definitions/include-path" + }, + "format": { + "$ref": "#/definitions/package-formats" + } } - }, - "build": { - "$ref": "#/definitions/build-section" - }, - "source": { - "type": "array", - "description": "A set of additional repositories where packages can be found.", - "additionalProperties": { - "$ref": "#/definitions/repository" + } + ] + } + }, + "exclude": { + "type": "array", + "description": "A list of files and folders to exclude." + }, + "dependencies": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.", + "required": [ + "python" + ], + "properties": { + "python": { + "type": "string", + "description": "The Python versions the package is compatible with." + } + }, + "$ref": "#/definitions/dependencies", + "additionalProperties": false + }, + "dev-dependencies": { + "type": "object", + "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).", + "$ref": "#/definitions/dependencies", + "additionalProperties": false + }, + "extras": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "group": { + "type": "object", + "description": "This represents groups of dependencies", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "object", + "description": "This represents a single dependency group", + "required": [ + "dependencies" + ], + "properties": { + "optional": { + "type": "boolean", + "description": "Whether the dependency group is optional or not" }, - "items": { - "$ref": "#/definitions/repository" - } - }, - "scripts": { - "type": "object", - "description": "A hash of scripts to be installed.", - "items": { - "type": "string" + "dependencies": { + "type": "object", + "description": "The dependencies of this dependency group", + "$ref": "#/definitions/dependencies", + "additionalProperties": false } - }, - "plugins": { - "type": "object", - "description": "A hash of hashes representing plugins", - "patternProperties": { - "^[a-zA-Z-_.0-9]+$": { - "type": "object", - "patternProperties": { - "^[a-zA-Z-_.0-9]+$": { - "type": "string" - } - } - } - } - }, - "urls": { - "type": "object", - "patternProperties": { - "^.+$": { - "type": "string", - "description": "The full url of the custom url." - } + }, + "additionalProperties": false + } + } + }, + "build": { + "$ref": "#/definitions/build-section" + }, + "scripts": { + "type": "object", + "description": "A hash of scripts to be installed.", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "oneOf": [ + { + "$ref": "#/definitions/script-legacy" + }, + { + "$ref": "#/definitions/script-table" } + ] } + } }, - "definitions": { - "authors": { - "type": "array", - "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", - "items": { - "type": "string" + "plugins": { + "type": "object", + "description": "A hash of hashes representing plugins", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "type": "string" } - }, - "maintainers": { - "type": "array", - "description": "List of maintainers, other than the original author(s), that upkeep the package.", - "items": { - "type": "string" + } + } + } + }, + "urls": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "string", + "description": "The full url of the custom url." + } + } + } + }, + "definitions": { + "authors": { + "type": "array", + "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.", + "items": { + "type": "string" + } + }, + "maintainers": { + "type": "array", + "description": "List of maintainers, other than the original author(s), that upkeep the package.", + "items": { + "type": "string" + } + }, + "include-path": { + "type": "string", + "description": "Path to file or directory to include." + }, + "package-format": { + "type": "string", + "enum": [ + "sdist", + "wheel" + ], + "description": "A Python packaging format." + }, + "package-formats": { + "oneOf": [ + { + "$ref": "#/definitions/package-format" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/package-format" + } + } + ], + "description": "The format(s) for which the package must be included." + }, + "dependencies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z-_.0-9]+$": { + "oneOf": [ + { + "$ref": "#/definitions/dependency" + }, + { + "$ref": "#/definitions/long-dependency" + }, + { + "$ref": "#/definitions/git-dependency" + }, + { + "$ref": "#/definitions/file-dependency" + }, + { + "$ref": "#/definitions/path-dependency" + }, + { + "$ref": "#/definitions/url-dependency" + }, + { + "$ref": "#/definitions/multiple-constraints-dependency" } + ] + } + } + }, + "dependency": { + "type": "string", + "description": "The constraint of the dependency." + }, + "long-dependency": { + "type": "object", + "required": [ + "version" + ], + "additionalProperties": false, + "properties": { + "version": { + "type": "string", + "description": "The constraint of the dependency." }, - "include-path": { - "type": "string", - "description": "Path to file or directory to include." - }, - "package-format": { - "type": "string", - "enum": ["sdist", "wheel"], - "description": "A Python packaging format." - }, - "package-formats": { - "oneOf": [ - {"$ref": "#/definitions/package-format"}, - {"type": "array", "items": {"$ref": "#/definitions/package-format"}} - ], - "description": "The format(s) for which the package must be included." + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." }, - "dependencies": { - "type": "object", - "patternProperties": { - "^[a-zA-Z-_.0-9]+$": { - "oneOf": [ - { - "$ref": "#/definitions/dependency" - }, - { - "$ref": "#/definitions/long-dependency" - }, - { - "$ref": "#/definitions/git-dependency" - }, - { - "$ref": "#/definitions/file-dependency" - }, - { - "$ref": "#/definitions/path-dependency" - }, - { - "$ref": "#/definitions/url-dependency" - }, - { - "$ref": "#/definitions/multiple-constraints-dependency" - } - ] - } - } + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." }, - "dependency": { - "type": "string", - "description": "The constraint of the dependency." + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." }, - "long-dependency": { - "type": "object", - "required": [ - "version" - ], - "additionalProperties": false, - "properties": { - "version": { - "type": "string", - "description": "The constraint of the dependency." - }, - "python": { - "type": "string", - "description": "The python versions for which the dependency should be installed." - }, - "platform": { - "type": "string", - "description": "The platform(s) for which the dependency should be installed." - }, - "markers": { - "type": "string", - "description": "The PEP 508 compliant environment markers for which the dependency should be installed." - }, - "allow-prereleases": { - "type": "boolean", - "description": "Whether the dependency allows prereleases or not." - }, - "allows-prereleases": { - "type": "boolean", - "description": "Whether the dependency allows prereleases or not." - }, - "optional": { - "type": "boolean", - "description": "Whether the dependency is optional or not." - }, - "extras": { - "type": "array", - "description": "The required extras for this dependency.", - "items": { - "type": "string" - } - }, - "source": { - "type": "string", - "description": "The exclusive source used to search for this dependency." - } - } + "allow-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." }, - "git-dependency": { - "type": "object", - "required": [ - "git" - ], - "additionalProperties": false, - "properties": { - "git": { - "type": "string", - "description": "The url of the git repository.", - "format": "uri" - }, - "branch": { - "type": "string", - "description": "The branch to checkout." - }, - "tag": { - "type": "string", - "description": "The tag to checkout." - }, - "rev": { - "type": "string", - "description": "The revision to checkout." - }, - "python": { - "type": "string", - "description": "The python versions for which the dependency should be installed." - }, - "platform": { - "type": "string", - "description": "The platform(s) for which the dependency should be installed." - }, - "markers": { - "type": "string", - "description": "The PEP 508 compliant environment markers for which the dependency should be installed." - }, - "allow-prereleases": { - "type": "boolean", - "description": "Whether the dependency allows prereleases or not." - }, - "allows-prereleases": { - "type": "boolean", - "description": "Whether the dependency allows prereleases or not." - }, - "optional": { - "type": "boolean", - "description": "Whether the dependency is optional or not." - }, - "extras": { - "type": "array", - "description": "The required extras for this dependency.", - "items": { - "type": "string" - } - }, - "develop": { - "type": "boolean", - "description": "Whether to install the dependency in development mode." - } - } + "allows-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." }, - "file-dependency": { - "type": "object", - "required": [ - "file" - ], - "additionalProperties": false, - "properties": { - "file": { - "type": "string", - "description": "The path to the file." - }, - "python": { - "type": "string", - "description": "The python versions for which the dependency should be installed." - }, - "platform": { - "type": "string", - "description": "The platform(s) for which the dependency should be installed." - }, - "markers": { - "type": "string", - "description": "The PEP 508 compliant environment markers for which the dependency should be installed." - }, - "optional": { - "type": "boolean", - "description": "Whether the dependency is optional or not." - }, - "extras": { - "type": "array", - "description": "The required extras for this dependency.", - "items": { - "type": "string" - } - } - } + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." }, - "path-dependency": { - "type": "object", - "required": [ - "path" - ], - "additionalProperties": false, - "properties": { - "path": { - "type": "string", - "description": "The path to the dependency." - }, - "python": { - "type": "string", - "description": "The python versions for which the dependency should be installed." - }, - "platform": { - "type": "string", - "description": "The platform(s) for which the dependency should be installed." - }, - "markers": { - "type": "string", - "description": "The PEP 508 compliant environment markers for which the dependency should be installed." - }, - "optional": { - "type": "boolean", - "description": "Whether the dependency is optional or not." - }, - "extras": { - "type": "array", - "description": "The required extras for this dependency.", - "items": { - "type": "string" - } - }, - "develop": { - "type": "boolean", - "description": "Whether to install the dependency in development mode." - } - } + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } }, - "url-dependency": { - "type": "object", - "required": [ - "url" - ], - "additionalProperties": false, - "properties": { - "url": { - "type": "string", - "description": "The url to the file." - }, - "python": { - "type": "string", - "description": "The python versions for which the dependency should be installed." - }, - "platform": { - "type": "string", - "description": "The platform(s) for which the dependency should be installed." - }, - "markers": { - "type": "string", - "description": "The PEP 508 compliant environment markers for which the dependency should be installed." - }, - "optional": { - "type": "boolean", - "description": "Whether the dependency is optional or not." - }, - "extras": { - "type": "array", - "description": "The required extras for this dependency.", - "items": { - "type": "string" - } - } - } + "source": { + "type": "string", + "description": "The exclusive source used to search for this dependency." + } + } + }, + "git-dependency": { + "type": "object", + "required": [ + "git" + ], + "additionalProperties": false, + "properties": { + "git": { + "type": "string", + "description": "The url of the git repository.", + "format": "uri" + }, + "branch": { + "type": "string", + "description": "The branch to checkout." + }, + "tag": { + "type": "string", + "description": "The tag to checkout." + }, + "rev": { + "type": "string", + "description": "The revision to checkout." + }, + "subdirectory": { + "type": "string", + "description": "The relative path to the directory where the package is located." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "allow-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." + }, + "allows-prereleases": { + "type": "boolean", + "description": "Whether the dependency allows prereleases or not." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." }, - "multiple-constraints-dependency": { - "type": "array", - "minItems": 1, - "items": { - "oneOf": [ - { - "$ref": "#/definitions/dependency" - }, - { - "$ref": "#/definitions/long-dependency" - }, - { - "$ref": "#/definitions/git-dependency" - }, - { - "$ref": "#/definitions/file-dependency" - }, - { - "$ref": "#/definitions/path-dependency" - }, - { - "$ref": "#/definitions/url-dependency" - } - ] - } + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + }, + "develop": { + "type": "boolean", + "description": "Whether to install the dependency in development mode." + } + } + }, + "file-dependency": { + "type": "object", + "required": [ + "file" + ], + "additionalProperties": false, + "properties": { + "file": { + "type": "string", + "description": "The path to the file." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." }, - "scripts": { - "type": "object", - "patternProperties": { - "^[a-zA-Z-_.0-9]+$": { - "oneOf": [ - { - "$ref": "#/definitions/script" - }, - { - "$ref": "#/definitions/extra-script" - } - ] - } - } + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + } + } + }, + "path-dependency": { + "type": "object", + "required": [ + "path" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "description": "The path to the dependency." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." }, - "script": { - "type": "string", - "description": "A simple script pointing to a callable object." + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + }, + "develop": { + "type": "boolean", + "description": "Whether to install the dependency in development mode." + } + } + }, + "url-dependency": { + "type": "object", + "required": [ + "url" + ], + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "description": "The url to the file." + }, + "subdirectory": { + "type": "string", + "description": "The relative path to the directory where the package is located." + }, + "python": { + "type": "string", + "description": "The python versions for which the dependency should be installed." + }, + "platform": { + "type": "string", + "description": "The platform(s) for which the dependency should be installed." + }, + "markers": { + "type": "string", + "description": "The PEP 508 compliant environment markers for which the dependency should be installed." + }, + "optional": { + "type": "boolean", + "description": "Whether the dependency is optional or not." }, - "extra-script": { - "type": "object", - "description": "A script that should be installed only if extras are activated.", - "additionalProperties": false, - "properties": { - "callable": { - "$ref": "#/definitions/script" - }, - "extras": { - "type": "array", - "description": "The required extras for this script.", - "items": { - "type": "string" - } - } - } + "extras": { + "type": "array", + "description": "The required extras for this dependency.", + "items": { + "type": "string" + } + } + } + }, + "multiple-constraints-dependency": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dependency" + }, + { + "$ref": "#/definitions/long-dependency" + }, + { + "$ref": "#/definitions/git-dependency" + }, + { + "$ref": "#/definitions/file-dependency" + }, + { + "$ref": "#/definitions/path-dependency" + }, + { + "$ref": "#/definitions/url-dependency" + } + ] + } + }, + "script-table": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/extra-script-legacy" + }, + { + "$ref": "#/definitions/extra-scripts" + } + ] + }, + "script-legacy": { + "type": "string", + "description": "A simple script pointing to a callable object." + }, + "extra-scripts": { + "type": "object", + "description": "Either a console entry point or a script file that'll be included in the distribution package.", + "additionalProperties": false, + "properties": { + "reference": { + "type": "string", + "description": "If type is file this is the relative path of the script file, if console it is the module name." + }, + "type": { + "description": "Value can be either file or console.", + "type": "string", + "enum": [ + "file", + "console" + ] }, - "repository": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "The name of the repository" - }, - "url": { - "type": "string", - "description": "The url of the repository", - "format": "uri" - }, - "default": { - "type": "boolean", - "description": "Make this repository the default (disable PyPI)" - }, - "secondary": { - "type": "boolean", - "description": "Declare this repository as secondary, i.e. it will only be looked up last for packages." - } - } + "extras": { + "type": "array", + "description": "The required extras for this script. Only applicable if type is console.", + "items": { + "type": "string" + } + } + }, + "required": [ + "reference", + "type" + ] + }, + "extra-script-legacy": { + "type": "object", + "description": "A script that should be installed only if extras are activated.", + "additionalProperties": false, + "properties": { + "callable": { + "$ref": "#/definitions/script-legacy", + "description": "The entry point of the script. Deprecated in favour of reference." }, - "build-script": { - "type": "string", - "description": "The python script file used to build extensions." + "extras": { + "type": "array", + "description": "The required extras for this script.", + "items": { + "type": "string" + } + } + } + }, + "build-script": { + "type": "string", + "description": "The python script file used to build extensions." + }, + "build-config": { + "type": "object", + "description": "Build specific configurations.", + "additionalProperties": false, + "properties": { + "generate-setup-file": { + "type": "boolean", + "description": "Generate and include a setup.py file in sdist.", + "default": true }, - "build-config": { - "type": "object", - "description": "Build specific configurations.", - "additionalProperties": false, - "properties": { - "generate-setup-file": { - "type": "boolean", - "description": "Generate and include a setup.py file in sdist.", - "default": true - }, - "script": { - "$ref": "#/definitions/build-script" - } - } + "script": { + "$ref": "#/definitions/build-script" + } + } + }, + "build-section": { + "oneOf": [ + { + "$ref": "#/definitions/build-script" }, - "build-section": { - "oneOf": [ - {"$ref": "#/definitions/build-script"}, - {"$ref": "#/definitions/build-config"} - ] + { + "$ref": "#/definitions/build-config" } + ] } + } } diff --git a/conda_lock/_vendor/poetry/core/masonry/__init__.py b/conda_lock/_vendor/poetry/core/masonry/__init__.py index ddd3a14f1..943204ad1 100644 --- a/conda_lock/_vendor/poetry/core/masonry/__init__.py +++ b/conda_lock/_vendor/poetry/core/masonry/__init__.py @@ -6,5 +6,3 @@ `flit `__ and adapted to work with the poetry codebase, so kudos to them for showing the way. """ - -from .builder import Builder diff --git a/conda_lock/_vendor/poetry/core/masonry/api.py b/conda_lock/_vendor/poetry/core/masonry/api.py index 019f53bc7..60b67104d 100644 --- a/conda_lock/_vendor/poetry/core/masonry/api.py +++ b/conda_lock/_vendor/poetry/core/masonry/api.py @@ -1,27 +1,24 @@ """ PEP-517 compliant buildsystem API """ +from __future__ import annotations + import logging +from pathlib import Path from typing import Any -from typing import Dict -from typing import List -from typing import Optional from conda_lock._vendor.poetry.core.factory import Factory -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils._compat import unicode - -from .builders.sdist import SdistBuilder -from .builders.wheel import WheelBuilder +from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder +from conda_lock._vendor.poetry.core.masonry.builders.wheel import WheelBuilder log = logging.getLogger(__name__) def get_requires_for_build_wheel( - config_settings=None, -): # type: (Optional[Dict[str, Any]]) -> List[str] + config_settings: dict[str, Any] | None = None, +) -> list[str]: """ Returns an additional list of requirements for building, as PEP508 strings, above and beyond those specified in the pyproject.toml file. @@ -38,53 +35,48 @@ def get_requires_for_build_wheel( def prepare_metadata_for_build_wheel( - metadata_directory, config_settings=None -): # type: (str, Optional[Dict[str, Any]]) -> str - poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False) + metadata_directory: str, config_settings: dict[str, Any] | None = None +) -> str: + poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False) builder = WheelBuilder(poetry) - - dist_info = Path(metadata_directory, builder.dist_info) - dist_info.mkdir(parents=True, exist_ok=True) - - if "scripts" in poetry.local_config or "plugins" in poetry.local_config: - with (dist_info / "entry_points.txt").open("w", encoding="utf-8") as f: - builder._write_entry_points(f) - - with (dist_info / "WHEEL").open("w", encoding="utf-8") as f: - builder._write_wheel_file(f) - - with (dist_info / "METADATA").open("w", encoding="utf-8") as f: - builder._write_metadata_file(f) - + metadata_path = Path(metadata_directory) + dist_info = builder.prepare_metadata(metadata_path) return dist_info.name def build_wheel( - wheel_directory, config_settings=None, metadata_directory=None -): # type: (str, Optional[Dict[str, Any]], Optional[str]) -> str + wheel_directory: str, + config_settings: dict[str, Any] | None = None, + metadata_directory: str | None = None, +) -> str: """Builds a wheel, places it in wheel_directory""" - poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False) + poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False) + metadata_path = None if metadata_directory is None else Path(metadata_directory) - return unicode(WheelBuilder.make_in(poetry, Path(wheel_directory))) + return WheelBuilder.make_in( + poetry, Path(wheel_directory), metadata_directory=metadata_path + ) def build_sdist( - sdist_directory, config_settings=None -): # type: (str, Optional[Dict[str, Any]]) -> str + sdist_directory: str, config_settings: dict[str, Any] | None = None +) -> str: """Builds an sdist, places it in sdist_directory""" - poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False) + poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False) path = SdistBuilder(poetry).build(Path(sdist_directory)) - return unicode(path.name) + return path.name def build_editable( - wheel_directory, config_settings=None, metadata_directory=None, -): # type: (str, Optional[Dict[str, Any]], Optional[str]) -> str - poetry = Factory().create_poetry(Path(".").resolve(), with_dev=False) + wheel_directory: str, + config_settings: dict[str, Any] | None = None, + metadata_directory: str | None = None, +) -> str: + poetry = Factory().create_poetry(Path(".").resolve(), with_groups=False) - return unicode(WheelBuilder.make_in(poetry, Path(wheel_directory), editable=True)) + return WheelBuilder.make_in(poetry, Path(wheel_directory), editable=True) get_requires_for_build_editable = get_requires_for_build_wheel diff --git a/conda_lock/_vendor/poetry/core/masonry/builder.py b/conda_lock/_vendor/poetry/core/masonry/builder.py index 85105cc49..5972640e0 100644 --- a/conda_lock/_vendor/poetry/core/masonry/builder.py +++ b/conda_lock/_vendor/poetry/core/masonry/builder.py @@ -1,35 +1,33 @@ -from typing import TYPE_CHECKING -from typing import Optional -from typing import Union - -from conda_lock._vendor.poetry.core.utils._compat import Path +from __future__ import annotations -from .builders.sdist import SdistBuilder -from .builders.wheel import WheelBuilder +from typing import TYPE_CHECKING if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.poetry import Poetry # noqa + from pathlib import Path + + from conda_lock._vendor.poetry.core.poetry import Poetry class Builder: - _FORMATS = { - "sdist": SdistBuilder, - "wheel": WheelBuilder, - } + def __init__(self, poetry: Poetry) -> None: + from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder + from conda_lock._vendor.poetry.core.masonry.builders.wheel import WheelBuilder - def __init__(self, poetry): # type: ("Poetry") -> None self._poetry = poetry - def build( - self, fmt, executable=None - ): # type: (str, Optional[Union[str, Path]]) -> None - if fmt in self._FORMATS: - builders = [self._FORMATS[fmt]] + self._formats = { + "sdist": SdistBuilder, + "wheel": WheelBuilder, + } + + def build(self, fmt: str, executable: str | Path | None = None) -> None: + if fmt in self._formats: + builders = [self._formats[fmt]] elif fmt == "all": - builders = self._FORMATS.values() + builders = list(self._formats.values()) else: - raise ValueError("Invalid format: {}".format(fmt)) + raise ValueError(f"Invalid format: {fmt}") for builder in builders: builder(self._poetry, executable=executable).build() diff --git a/conda_lock/_vendor/poetry/core/masonry/builders/__init__.py b/conda_lock/_vendor/poetry/core/masonry/builders/__init__.py index 20d725b77..e69de29bb 100644 --- a/conda_lock/_vendor/poetry/core/masonry/builders/__init__.py +++ b/conda_lock/_vendor/poetry/core/masonry/builders/__init__.py @@ -1,2 +0,0 @@ -from .sdist import SdistBuilder -from .wheel import WheelBuilder diff --git a/conda_lock/_vendor/poetry/core/masonry/builders/builder.py b/conda_lock/_vendor/poetry/core/masonry/builders/builder.py index f95e5b03a..88fc56259 100644 --- a/conda_lock/_vendor/poetry/core/masonry/builders/builder.py +++ b/conda_lock/_vendor/poetry/core/masonry/builders/builder.py @@ -1,31 +1,17 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import logging import re -import shutil import sys -import tempfile +import warnings from collections import defaultdict -from contextlib import contextmanager +from pathlib import Path from typing import TYPE_CHECKING -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import Set -from typing import Union - -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils._compat import to_str -from conda_lock._vendor.poetry.core.vcs import get_vcs - -from ..metadata import Metadata -from ..utils.module import Module -from ..utils.package_include import PackageInclude if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.poetry import Poetry # noqa + from conda_lock._vendor.poetry.core.poetry import Poetry AUTHOR_REGEX = re.compile(r"(?u)^(?P[- .,\w\d'’\"()]+) <(?P.+?)>$") @@ -40,21 +26,33 @@ logger = logging.getLogger(__name__) -class Builder(object): - format = None # type: Optional[str] +class Builder: + format: str | None = None def __init__( - self, poetry, ignore_packages_formats=False, executable=None - ): # type: ("Poetry", bool, Optional[Union[Path, str]]) -> None + self, + poetry: Poetry, + ignore_packages_formats: bool = False, + executable: Path | None = None, + ) -> None: + from conda_lock._vendor.poetry.core.masonry.metadata import Metadata + from conda_lock._vendor.poetry.core.masonry.utils.module import Module + self._poetry = poetry self._package = poetry.package - self._path = poetry.file.parent - self._excluded_files = None # type: Optional[Set[str]] + self._path: Path = poetry.file.parent + self._excluded_files: set[str] | None = None self._executable = Path(executable or sys.executable) packages = [] for p in self._package.packages: - formats = p.get("format", []) + formats = p.get("format") or None + + # Default to including the package in both sdist & wheel + # if the `format` key is not provided in the inline include table. + if formats is None: + formats = ["sdist", "wheel"] + if not isinstance(formats, list): formats = [formats] @@ -92,14 +90,20 @@ def __init__( self._meta = Metadata.from_package(self._package) @property - def executable(self): # type: () -> Path + def executable(self) -> Path: return self._executable - def build(self): # type: () -> None + @property + def default_target_dir(self) -> Path: + return self._path / "dist" + + def build(self, target_dir: Path | None) -> Path: raise NotImplementedError() - def find_excluded_files(self): # type: () -> Set[str] + def find_excluded_files(self, fmt: str | None = None) -> set[str]: if self._excluded_files is None: + from conda_lock._vendor.poetry.core.vcs import get_vcs + # Checking VCS vcs = get_vcs(self._path) if not vcs: @@ -107,38 +111,37 @@ def find_excluded_files(self): # type: () -> Set[str] else: vcs_ignored_files = set(vcs.get_ignored_files()) - explicitely_excluded = set() + explicitly_excluded = set() for excluded_glob in self._package.exclude: for excluded in self._path.glob(str(excluded_glob)): - explicitely_excluded.add( + explicitly_excluded.add( Path(excluded).relative_to(self._path).as_posix() ) - explicitely_included = set() + explicitly_included = set() for inc in self._package.include: + if fmt and inc["format"] and fmt not in inc["format"]: + continue + included_glob = inc["path"] for included in self._path.glob(str(included_glob)): - explicitely_included.add( + explicitly_included.add( Path(included).relative_to(self._path).as_posix() ) - ignored = (vcs_ignored_files | explicitely_excluded) - explicitely_included - result = set() - for file in ignored: - result.add(file) + ignored = (vcs_ignored_files | explicitly_excluded) - explicitly_included + for ignored_file in ignored: + logger.debug(f"Ignoring: {ignored_file}") - # The list of excluded files might be big and we will do a lot - # containment check (x in excluded). - # Returning a set make those tests much much faster. - self._excluded_files = result + self._excluded_files = ignored return self._excluded_files - def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool + def is_excluded(self, filepath: str | Path) -> bool: exclude_path = Path(filepath) while True: - if exclude_path.as_posix() in self.find_excluded_files(): + if exclude_path.as_posix() in self.find_excluded_files(fmt=self.format): return True if len(exclude_path.parts) > 1: @@ -148,12 +151,12 @@ def is_excluded(self, filepath): # type: (Union[str, Path]) -> bool return False - def find_files_to_add( - self, exclude_build=True - ): # type: (bool) -> Set[BuildIncludeFile] + def find_files_to_add(self, exclude_build: bool = True) -> set[BuildIncludeFile]: """ Finds all files to add to the tarball """ + from conda_lock._vendor.poetry.core.masonry.utils.package_include import PackageInclude + to_add = set() for include in self._module.includes: @@ -164,13 +167,22 @@ def find_files_to_add( if "__pycache__" in str(file): continue + if ( + isinstance(include, PackageInclude) + and include.source + and self.format == "wheel" + ): + source_root = include.base + else: + source_root = self._path + if file.is_dir(): if self.format in formats: for current_file in file.glob("**/*"): include_file = BuildIncludeFile( path=current_file, project_root=self._path, - source_root=self._path, + source_root=source_root, ) if not current_file.is_dir() and not self.is_excluded( @@ -179,15 +191,6 @@ def find_files_to_add( to_add.add(include_file) continue - if ( - isinstance(include, PackageInclude) - and include.source - and self.format == "wheel" - ): - source_root = include.base - else: - source_root = self._path - include_file = BuildIncludeFile( path=file, project_root=self._path, source_root=source_root ) @@ -200,11 +203,7 @@ def find_files_to_add( if file.suffix == ".pyc": continue - if file in to_add: - # Skip duplicates - continue - - logger.debug("Adding: {}".format(str(file))) + logger.debug(f"Adding: {str(file)}") to_add.add(include_file) # add build script if it is specified and explicitly required @@ -219,117 +218,149 @@ def find_files_to_add( return to_add - def get_metadata_content(self): # type: () -> str + def get_metadata_content(self) -> str: content = METADATA_BASE.format( name=self._meta.name, version=self._meta.version, - summary=to_str(self._meta.summary), + summary=str(self._meta.summary), ) # Optional fields if self._meta.home_page: - content += "Home-page: {}\n".format(self._meta.home_page) + content += f"Home-page: {self._meta.home_page}\n" if self._meta.license: - content += "License: {}\n".format(self._meta.license) + content += f"License: {self._meta.license}\n" if self._meta.keywords: - content += "Keywords: {}\n".format(self._meta.keywords) + content += f"Keywords: {self._meta.keywords}\n" if self._meta.author: - content += "Author: {}\n".format(to_str(self._meta.author)) + content += f"Author: {str(self._meta.author)}\n" if self._meta.author_email: - content += "Author-email: {}\n".format(to_str(self._meta.author_email)) + content += f"Author-email: {str(self._meta.author_email)}\n" if self._meta.maintainer: - content += "Maintainer: {}\n".format(to_str(self._meta.maintainer)) + content += f"Maintainer: {str(self._meta.maintainer)}\n" if self._meta.maintainer_email: - content += "Maintainer-email: {}\n".format( - to_str(self._meta.maintainer_email) - ) + content += f"Maintainer-email: {str(self._meta.maintainer_email)}\n" if self._meta.requires_python: - content += "Requires-Python: {}\n".format(self._meta.requires_python) + content += f"Requires-Python: {self._meta.requires_python}\n" for classifier in self._meta.classifiers: - content += "Classifier: {}\n".format(classifier) + content += f"Classifier: {classifier}\n" for extra in sorted(self._meta.provides_extra): - content += "Provides-Extra: {}\n".format(extra) + content += f"Provides-Extra: {extra}\n" for dep in sorted(self._meta.requires_dist): - content += "Requires-Dist: {}\n".format(dep) + content += f"Requires-Dist: {dep}\n" for url in sorted(self._meta.project_urls, key=lambda u: u[0]): - content += "Project-URL: {}\n".format(to_str(url)) + content += f"Project-URL: {str(url)}\n" if self._meta.description_content_type: - content += "Description-Content-Type: {}\n".format( - self._meta.description_content_type + content += ( + f"Description-Content-Type: {self._meta.description_content_type}\n" ) if self._meta.description is not None: - content += "\n" + to_str(self._meta.description) + "\n" + content += "\n" + str(self._meta.description) + "\n" return content - def convert_entry_points(self): # type: () -> Dict[str, List[str]] + def convert_entry_points(self) -> dict[str, list[str]]: result = defaultdict(list) # Scripts -> Entry points - for name, ep in self._poetry.local_config.get("scripts", {}).items(): - extras = "" - if isinstance(ep, dict): - extras = "[{}]".format(", ".join(ep["extras"])) - ep = ep["callable"] + for name, specification in self._poetry.local_config.get("scripts", {}).items(): + if isinstance(specification, str): + # TODO: deprecate this in favour or reference + specification = {"reference": specification, "type": "console"} + + if "callable" in specification: + warnings.warn( + f"Use of callable in script specification ({name}) is deprecated." + " Use reference instead.", + DeprecationWarning, + ) + specification = { + "reference": specification["callable"], + "type": "console", + } - result["console_scripts"].append("{} = {}{}".format(name, ep, extras)) + if specification.get("type") != "console": + continue + + extras = specification.get("extras", []) + extras = f"[{', '.join(extras)}]" if extras else "" + reference = specification.get("reference") + + if reference: + result["console_scripts"].append(f"{name} = {reference}{extras}") # Plugins -> entry points plugins = self._poetry.local_config.get("plugins", {}) for groupname, group in plugins.items(): - for name, ep in sorted(group.items()): - result[groupname].append("{} = {}".format(name, ep)) + for name, specification in sorted(group.items()): + result[groupname].append(f"{name} = {specification}") for groupname in result: result[groupname] = sorted(result[groupname]) return dict(result) + def convert_script_files(self) -> list[Path]: + script_files: list[Path] = [] + + for name, specification in self._poetry.local_config.get("scripts", {}).items(): + if isinstance(specification, dict) and specification.get("type") == "file": + source = specification["reference"] + + if Path(source).is_absolute(): + raise RuntimeError( + f"{source} in {name} is an absolute path. Expected relative" + " path." + ) + + abs_path = Path.joinpath(self._path, source) + + if not abs_path.exists(): + raise RuntimeError( + f"{abs_path} in script specification ({name}) is not found." + ) + + if not abs_path.is_file(): + raise RuntimeError( + f"{abs_path} in script specification ({name}) is not a file." + ) + + script_files.append(abs_path) + + return script_files + @classmethod - def convert_author(cls, author): # type: (str) -> Dict[str, str] + def convert_author(cls, author: str) -> dict[str, str]: m = AUTHOR_REGEX.match(author) + if m is None: + raise RuntimeError(f"{author} does not match regex") name = m.group("name") email = m.group("email") return {"name": name, "email": email} - @classmethod - @contextmanager - def temporary_directory(cls, *args, **kwargs): # type: (*Any, **Any) -> None - try: - from tempfile import TemporaryDirectory - - with TemporaryDirectory(*args, **kwargs) as name: - yield name - except ImportError: - name = tempfile.mkdtemp(*args, **kwargs) - - yield name - - shutil.rmtree(name) - class BuildIncludeFile: def __init__( self, - path, # type: Union[Path, str] - project_root, # type: Union[Path, str] - source_root=None, # type: Optional[Union[Path, str]] - ): + path: Path | str, + project_root: Path | str, + source_root: Path | str | None = None, + ) -> None: """ :param project_root: the full path of the project's root :param path: a full path to the file to be included @@ -343,32 +374,25 @@ def __init__( else: self.path = self.path - try: - self.path = self.path.resolve() - except FileNotFoundError: - # this is an issue in in python 3.5, since resolve uses strict=True by - # default, this workaround needs to be maintained till python 2.7 and - # python 3.5 are dropped, until we can use resolve(strict=False). - pass + self.path = self.path.resolve() - def __eq__(self, other): # type: (Union[BuildIncludeFile, Path]) -> bool - if hasattr(other, "path"): - return self.path == other.path - return self.path == other + def __eq__(self, other: object) -> bool: + if not isinstance(other, BuildIncludeFile): + return False - def __ne__(self, other): # type: (Union[BuildIncludeFile, Path]) -> bool - return not self.__eq__(other) + return self.path == other.path - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(self.path) - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return str(self.path) - def relative_to_project_root(self): # type: () -> Path + def relative_to_project_root(self) -> Path: return self.path.relative_to(self.project_root) - def relative_to_source_root(self): # type: () -> Path + def relative_to_source_root(self) -> Path: if self.source_root is not None: return self.path.relative_to(self.source_root) + return self.path diff --git a/conda_lock/_vendor/poetry/core/masonry/builders/sdist.py b/conda_lock/_vendor/poetry/core/masonry/builders/sdist.py index cf96f88d7..318f0841e 100644 --- a/conda_lock/_vendor/poetry/core/masonry/builders/sdist.py +++ b/conda_lock/_vendor/poetry/core/masonry/builders/sdist.py @@ -1,40 +1,32 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import logging import os import re import tarfile -import time from collections import defaultdict from contextlib import contextmanager from copy import copy from gzip import GzipFile from io import BytesIO +from pathlib import Path from posixpath import join as pjoin from pprint import pformat -from tarfile import TarInfo from typing import TYPE_CHECKING -from typing import Dict from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple - -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils._compat import decode -from conda_lock._vendor.poetry.core.utils._compat import encode -from conda_lock._vendor.poetry.core.utils._compat import to_str -from ..utils.helpers import normalize_file_permissions -from ..utils.package_include import PackageInclude -from .builder import Builder -from .builder import BuildIncludeFile +from conda_lock._vendor.poetry.core.masonry.builders.builder import Builder +from conda_lock._vendor.poetry.core.masonry.builders.builder import BuildIncludeFile +from conda_lock._vendor.poetry.core.masonry.utils.helpers import distribution_name if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.packages import Dependency # noqa - from conda_lock._vendor.poetry.core.packages import ProjectPackage # noqa + from tarfile import TarInfo + + from conda_lock._vendor.poetry.core.masonry.utils.package_include import PackageInclude + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage SETUP = """\ # -*- coding: utf-8 -*- @@ -62,27 +54,27 @@ class SdistBuilder(Builder): - format = "sdist" - def build(self, target_dir=None): # type: (Optional[Path]) -> Path + def build( + self, + target_dir: Path | None = None, + ) -> Path: logger.info("Building sdist") - if target_dir is None: - target_dir = self._path / "dist" + target_dir = target_dir or self.default_target_dir if not target_dir.exists(): target_dir.mkdir(parents=True) - target = target_dir / "{}-{}.tar.gz".format( - self._package.pretty_name, self._meta.version - ) + name = distribution_name(self._package.name) + target = target_dir / f"{name}-{self._meta.version}.tar.gz" gz = GzipFile(target.as_posix(), mode="wb", mtime=0) tar = tarfile.TarFile( target.as_posix(), mode="w", fileobj=gz, format=tarfile.PAX_FORMAT ) try: - tar_dir = "{}-{}".format(self._package.pretty_name, self._meta.version) + tar_dir = f"{name}-{self._meta.version}" files_to_add = self.find_files_to_add(exclude_build=False) @@ -103,32 +95,36 @@ def build(self, target_dir=None): # type: (Optional[Path]) -> Path setup = self.build_setup() tar_info = tarfile.TarInfo(pjoin(tar_dir, "setup.py")) tar_info.size = len(setup) - tar_info.mtime = time.time() + tar_info.mtime = 0 + tar_info = self.clean_tarinfo(tar_info) tar.addfile(tar_info, BytesIO(setup)) pkg_info = self.build_pkg_info() tar_info = tarfile.TarInfo(pjoin(tar_dir, "PKG-INFO")) tar_info.size = len(pkg_info) - tar_info.mtime = time.time() + tar_info.mtime = 0 + tar_info = self.clean_tarinfo(tar_info) tar.addfile(tar_info, BytesIO(pkg_info)) finally: tar.close() gz.close() - logger.info("Built {}".format(target.name)) + logger.info(f"Built {target.name}") return target - def build_setup(self): # type: () -> bytes + def build_setup(self) -> bytes: + from conda_lock._vendor.poetry.core.masonry.utils.package_include import PackageInclude + before, extra, after = [], [], [] - package_dir = {} + package_dir: dict[str, str] = {} # If we have a build script, use it if self._package.build_script: - after += [ - "from {} import *".format(self._package.build_script.split(".")[0]), - "build(setup_kwargs)", - ] + import_name = ".".join( + Path(self._package.build_script).with_suffix("").parts + ) + after += [f"from {import_name} import *", "build(setup_kwargs)"] modules = [] packages = [] @@ -142,7 +138,14 @@ def build_setup(self): # type: () -> bytes pkg_dir, _packages, _package_data = self.find_packages(include) if pkg_dir is not None: - package_dir[""] = os.path.relpath(pkg_dir, str(self._path)) + pkg_root = os.path.relpath(pkg_dir, str(self._path)) + if "" in package_dir: + package_dir.update( + (p, os.path.join(pkg_root, p.replace(".", "/"))) + for p in _packages + ) + else: + package_dir[""] = pkg_root packages += [p for p in _packages if p not in packages] package_data.update(_package_data) @@ -158,63 +161,65 @@ def build_setup(self): # type: () -> bytes pass if package_dir: - before.append("package_dir = \\\n{}\n".format(pformat(package_dir))) + before.append(f"package_dir = \\\n{pformat(package_dir)}\n") extra.append("'package_dir': package_dir,") if packages: - before.append("packages = \\\n{}\n".format(pformat(sorted(packages)))) + before.append(f"packages = \\\n{pformat(sorted(packages))}\n") extra.append("'packages': packages,") if package_data: - before.append("package_data = \\\n{}\n".format(pformat(package_data))) + before.append(f"package_data = \\\n{pformat(package_data)}\n") extra.append("'package_data': package_data,") if modules: - before.append("modules = \\\n{}".format(pformat(modules))) - extra.append("'py_modules': modules,".format()) + before.append(f"modules = \\\n{pformat(modules)}") + extra.append("'py_modules': modules,") dependencies, extras = self.convert_dependencies( self._package, self._package.requires ) if dependencies: - before.append( - "install_requires = \\\n{}\n".format(pformat(sorted(dependencies))) - ) + before.append(f"install_requires = \\\n{pformat(sorted(dependencies))}\n") extra.append("'install_requires': install_requires,") if extras: - before.append("extras_require = \\\n{}\n".format(pformat(extras))) + before.append(f"extras_require = \\\n{pformat(extras)}\n") extra.append("'extras_require': extras_require,") entry_points = self.convert_entry_points() if entry_points: - before.append("entry_points = \\\n{}\n".format(pformat(entry_points))) + before.append(f"entry_points = \\\n{pformat(entry_points)}\n") extra.append("'entry_points': entry_points,") + script_files = self.convert_script_files() + if script_files: + rel_paths = [str(p.relative_to(self._path)) for p in script_files] + before.append(f"scripts = \\\n{pformat(rel_paths)}\n") + extra.append("'scripts': scripts,") + if self._package.python_versions != "*": python_requires = self._meta.requires_python - extra.append("'python_requires': {!r},".format(python_requires)) - - return encode( - SETUP.format( - before="\n".join(before), - name=to_str(self._meta.name), - version=to_str(self._meta.version), - description=to_str(self._meta.summary), - long_description=to_str(self._meta.description), - author=to_str(self._meta.author), - author_email=to_str(self._meta.author_email), - maintainer=to_str(self._meta.maintainer), - maintainer_email=to_str(self._meta.maintainer_email), - url=to_str(self._meta.home_page), - extra="\n ".join(extra), - after="\n".join(after), - ) - ) + extra.append(f"'python_requires': {python_requires!r},") + + return SETUP.format( + before="\n".join(before), + name=str(self._meta.name), + version=self._meta.version, + description=str(self._meta.summary), + long_description=str(self._meta.description), + author=str(self._meta.author), + author_email=str(self._meta.author_email), + maintainer=str(self._meta.maintainer), + maintainer_email=str(self._meta.maintainer_email), + url=str(self._meta.home_page), + extra="\n ".join(extra), + after="\n".join(after), + ).encode() @contextmanager - def setup_py(self): # type: () -> Iterator[Path] + def setup_py(self) -> Iterator[Path]: setup = self._path / "setup.py" has_setup = setup.exists() @@ -222,19 +227,19 @@ def setup_py(self): # type: () -> Iterator[Path] logger.warning("A setup.py file already exists. Using it.") else: with setup.open("w", encoding="utf-8") as f: - f.write(decode(self.build_setup())) + f.write(self.build_setup().decode()) yield setup if not has_setup: setup.unlink() - def build_pkg_info(self): # type: () -> bytes - return encode(self.get_metadata_content()) + def build_pkg_info(self) -> bytes: + return self.get_metadata_content().encode() def find_packages( - self, include - ): # type: (PackageInclude) -> Tuple[str, List[str], dict] + self, include: PackageInclude + ) -> tuple[str | None, list[str], dict[str, list[str]]]: """ Discover subpackages and data. @@ -247,14 +252,14 @@ def find_packages( base = str(include.elements[0].parent) pkg_name = include.package - pkg_data = defaultdict(list) - # Undocumented distutils feature: + pkg_data: dict[str, list[str]] = defaultdict(list) + # Undocumented setup() feature: # the empty string matches all package names pkg_data[""].append("*") packages = [pkg_name] subpkg_paths = set() - def find_nearest_pkg(rel_path): # type: (str) -> Tuple[str, str] + def find_nearest_pkg(rel_path: str) -> tuple[str, str]: parts = rel_path.split(os.sep) for i in reversed(range(1, len(parts))): ancestor = "/".join(parts[:i]) @@ -265,7 +270,7 @@ def find_nearest_pkg(rel_path): # type: (str) -> Tuple[str, str] # Relative to the top-level package return pkg_name, Path(rel_path).as_posix() - for path, dirnames, filenames in os.walk(str(base), topdown=True): + for path, _dirnames, filenames in os.walk(str(base), topdown=True): if os.path.basename(path) == "__pycache__": continue @@ -313,37 +318,36 @@ def find_nearest_pkg(rel_path): # type: (str) -> Tuple[str, str] return pkgdir, sorted(packages), pkg_data - def find_files_to_add( - self, exclude_build=False - ): # type: (bool) -> Set[BuildIncludeFile] - to_add = super(SdistBuilder, self).find_files_to_add(exclude_build) + def find_files_to_add(self, exclude_build: bool = False) -> set[BuildIncludeFile]: + to_add = super().find_files_to_add(exclude_build) # add any additional files, starting with all LICENSE files - additional_files = { - license_file for license_file in self._path.glob("LICENSE*") - } + additional_files = set(self._path.glob("LICENSE*")) + + # add script files + additional_files.update(self.convert_script_files()) # Include project files - additional_files.add("pyproject.toml") + additional_files.add(Path("pyproject.toml")) # add readme if it is specified if "readme" in self._poetry.local_config: additional_files.add(self._poetry.local_config["readme"]) - for file in additional_files: + for additional_file in additional_files: file = BuildIncludeFile( - path=file, project_root=self._path, source_root=self._path + path=additional_file, project_root=self._path, source_root=self._path ) if file.path.exists(): - logger.debug("Adding: {}".format(file.relative_to_source_root())) + logger.debug(f"Adding: {file.relative_to_source_root()}") to_add.add(file) return to_add @classmethod def convert_dependencies( - cls, package, dependencies - ): # type: ("ProjectPackage", List["Dependency"]) -> Tuple[List[str], Dict[str, List[str]]] + cls, package: ProjectPackage, dependencies: list[Dependency] + ) -> tuple[list[str], dict[str, list[str]]]: main = [] extras = defaultdict(list) req_regex = re.compile(r"^(.+) \((.+)\)$") @@ -353,9 +357,7 @@ def convert_dependencies( for extra_name, reqs in package.extras.items(): for req in reqs: if req.name == dependency.name: - requirement = to_str( - dependency.to_pep_508(with_extras=False) - ) + requirement = dependency.to_pep_508(with_extras=False) if ";" in requirement: requirement, conditions = requirement.split(";") @@ -379,7 +381,7 @@ def convert_dependencies( extras[extra_name].append(requirement) continue - requirement = to_str(dependency.to_pep_508()) + requirement = dependency.to_pep_508() if ";" in requirement: requirement, conditions = requirement.split(";") @@ -400,7 +402,7 @@ def convert_dependencies( return main, dict(extras) @classmethod - def clean_tarinfo(cls, tar_info): # type: (TarInfo) -> TarInfo + def clean_tarinfo(cls, tar_info: TarInfo) -> TarInfo: """ Clean metadata from a TarInfo object to make it more reproducible. @@ -409,6 +411,8 @@ def clean_tarinfo(cls, tar_info): # type: (TarInfo) -> TarInfo - Normalise permissions to 644 or 755 - Set mtime if not None """ + from conda_lock._vendor.poetry.core.masonry.utils.helpers import normalize_file_permissions + ti = copy(tar_info) ti.uid = 0 ti.gid = 0 diff --git a/conda_lock/_vendor/poetry/core/masonry/builders/wheel.py b/conda_lock/_vendor/poetry/core/masonry/builders/wheel.py index a60c351e3..3301da03d 100644 --- a/conda_lock/_vendor/poetry/core/masonry/builders/wheel.py +++ b/conda_lock/_vendor/poetry/core/masonry/builders/wheel.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import annotations import contextlib import csv @@ -12,36 +12,32 @@ import zipfile from base64 import urlsafe_b64encode -from io import BytesIO from io import StringIO +from pathlib import Path from typing import TYPE_CHECKING from typing import Iterator -from typing import Optional from typing import TextIO -from typing import Union from packaging.tags import sys_tags from conda_lock._vendor.poetry.core import __version__ -from conda_lock._vendor.poetry.core.semver import parse_constraint -from conda_lock._vendor.poetry.core.utils._compat import PY2 -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils._compat import decode - -from ..utils.helpers import escape_name -from ..utils.helpers import escape_version -from ..utils.helpers import normalize_file_permissions -from ..utils.package_include import PackageInclude -from .builder import Builder -from .sdist import SdistBuilder +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint +from conda_lock._vendor.poetry.core.masonry.builders.builder import Builder +from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder +from conda_lock._vendor.poetry.core.masonry.utils.helpers import distribution_name +from conda_lock._vendor.poetry.core.masonry.utils.helpers import normalize_file_permissions +from conda_lock._vendor.poetry.core.masonry.utils.package_include import PackageInclude +from conda_lock._vendor.poetry.core.utils.helpers import temporary_directory if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.poetry import Poetry # noqa + from packaging.utils import NormalizedName + + from conda_lock._vendor.poetry.core.poetry import Poetry wheel_file_template = """\ Wheel-Version: 1.0 -Generator: poetry {version} +Generator: poetry-core {version} Root-Is-Purelib: {pure_lib} Tag: {tag} """ @@ -53,43 +49,57 @@ class WheelBuilder(Builder): format = "wheel" def __init__( - self, poetry, target_dir=None, original=None, executable=None, editable=False, - ): # type: ("Poetry", Optional[Path], Optional[Path], Optional[str], bool) -> None - super(WheelBuilder, self).__init__(poetry, executable=executable) - - self._records = [] + self, + poetry: Poetry, + original: Path | None = None, + executable: Path | None = None, + editable: bool = False, + metadata_directory: Path | None = None, + ) -> None: + super().__init__(poetry, executable=executable) + + self._records: list[tuple[str, str, int]] = [] self._original_path = self._path - self._target_dir = target_dir or (self._poetry.file.parent / "dist") if original: - self._original_path = original.file.parent + self._original_path = original.parent self._editable = editable + self._metadata_directory = metadata_directory @classmethod def make_in( - cls, poetry, directory=None, original=None, executable=None, editable=False, - ): # type: ("Poetry", Optional[Path], Optional[Path], Optional[str], bool) -> str + cls, + poetry: Poetry, + directory: Path | None = None, + original: Path | None = None, + executable: Path | None = None, + editable: bool = False, + metadata_directory: Path | None = None, + ) -> str: wb = WheelBuilder( poetry, - target_dir=directory, original=original, executable=executable, editable=editable, + metadata_directory=metadata_directory, ) - wb.build() + wb.build(target_dir=directory) return wb.wheel_filename @classmethod - def make(cls, poetry, executable=None): # type: ("Poetry", Optional[str]) -> None + def make(cls, poetry: Poetry, executable: Path | None = None) -> None: """Build a wheel in the dist/ directory, and optionally upload it.""" cls.make_in(poetry, executable=executable) - def build(self): # type: () -> None + def build( + self, + target_dir: Path | None = None, + ) -> Path: logger.info("Building wheel") - dist_dir = self._target_dir - if not dist_dir.exists(): - dist_dir.mkdir() + target_dir = target_dir or self.default_target_dir + if not target_dir.exists(): + target_dir.mkdir() (fd, temp_path) = tempfile.mkstemp(suffix=".whl") @@ -97,32 +107,39 @@ def build(self): # type: () -> None new_mode = normalize_file_permissions(st_mode) os.chmod(temp_path, new_mode) - with os.fdopen(fd, "w+b") as fd_file: - with zipfile.ZipFile( - fd_file, mode="w", compression=zipfile.ZIP_DEFLATED - ) as zip_file: - if not self._editable: - if not self._poetry.package.build_should_generate_setup(): - self._build(zip_file) - self._copy_module(zip_file) - else: - self._copy_module(zip_file) - self._build(zip_file) - else: - self._build(zip_file) - self._add_pth(zip_file) - - self._write_metadata(zip_file) - self._write_record(zip_file) - - wheel_path = dist_dir / self.wheel_filename + with os.fdopen(fd, "w+b") as fd_file, zipfile.ZipFile( + fd_file, mode="w", compression=zipfile.ZIP_DEFLATED + ) as zip_file: + if self._editable: + self._build(zip_file) + self._add_pth(zip_file) + elif self._poetry.package.build_should_generate_setup(): + self._copy_module(zip_file) + self._build(zip_file) + else: + self._build(zip_file) + self._copy_module(zip_file) + + self._copy_file_scripts(zip_file) + + if self._metadata_directory is None: + with temporary_directory() as temp_dir: + metadata_directory = self.prepare_metadata(Path(temp_dir)) + self._copy_dist_info(zip_file, metadata_directory) + else: + self._copy_dist_info(zip_file, self._metadata_directory) + + self._write_record(zip_file) + + wheel_path = target_dir / self.wheel_filename if wheel_path.exists(): wheel_path.unlink() shutil.move(temp_path, str(wheel_path)) - logger.info("Built {}".format(self.wheel_filename)) + logger.info(f"Built {self.wheel_filename}") + return wheel_path - def _add_pth(self, wheel): # type: (zipfile.ZipFile) -> None + def _add_pth(self, wheel: zipfile.ZipFile) -> None: paths = set() for include in self._module.includes: if isinstance(include, PackageInclude) and ( @@ -139,7 +156,7 @@ def _add_pth(self, wheel): # type: (zipfile.ZipFile) -> None with self._write_to_zip(wheel, str(pth_file)) as f: f.write(content) - def _build(self, wheel): # type: (zipfile.ZipFile) -> None + def _build(self, wheel: zipfile.ZipFile) -> None: if self._package.build_script: if not self._poetry.package.build_should_generate_setup(): # Since we have a build script but no setup.py generation is required, @@ -164,14 +181,14 @@ def _build(self, wheel): # type: (zipfile.ZipFile) -> None os.chdir(current_path) build_dir = self._path / "build" - lib = list(build_dir.glob("lib.*")) - if not lib: + libs: list[Path] = list(build_dir.glob("lib.*")) + if not libs: # The result of building the extensions # does not exist, this may due to conditional # builds, so we assume that it's okay return - lib = lib[0] + lib = libs[0] for pkg in lib.glob("**/*"): if pkg.is_dir() or self.is_excluded(pkg): @@ -182,11 +199,21 @@ def _build(self, wheel): # type: (zipfile.ZipFile) -> None if rel_path in wheel.namelist(): continue - logger.debug("Adding: {}".format(rel_path)) + logger.debug(f"Adding: {rel_path}") self._add_file(wheel, pkg, rel_path) - def _run_build_command(self, setup): # type: (Path) -> None + def _copy_file_scripts(self, wheel: zipfile.ZipFile) -> None: + file_scripts = self.convert_script_files() + + for abs_path in file_scripts: + self._add_file( + wheel, + abs_path, + Path.joinpath(Path(self.wheel_data_folder), "scripts", abs_path.name), + ) + + def _run_build_command(self, setup: Path) -> None: subprocess.check_call( [ self.executable.as_posix(), @@ -197,50 +224,59 @@ def _run_build_command(self, setup): # type: (Path) -> None ] ) - def _run_build_script(self, build_script): # type: (str) -> None - logger.debug("Executing build script: {}".format(build_script)) + def _run_build_script(self, build_script: str) -> None: + logger.debug(f"Executing build script: {build_script}") subprocess.check_call([self.executable.as_posix(), build_script]) - def _copy_module(self, wheel): # type: (zipfile.ZipFile) -> None + def _copy_module(self, wheel: zipfile.ZipFile) -> None: to_add = self.find_files_to_add() # Walk the files and compress them, # sorting everything so the order is stable. - for file in sorted(list(to_add), key=lambda x: x.path): + for file in sorted(to_add, key=lambda x: x.path): self._add_file(wheel, file.path, file.relative_to_source_root()) - def _write_metadata(self, wheel): # type: (zipfile.ZipFile) -> None + def prepare_metadata(self, metadata_directory: Path) -> Path: + dist_info = metadata_directory / self.dist_info + dist_info.mkdir(parents=True, exist_ok=True) + if ( "scripts" in self._poetry.local_config or "plugins" in self._poetry.local_config ): - with self._write_to_zip(wheel, self.dist_info + "/entry_points.txt") as f: + with (dist_info / "entry_points.txt").open( + "w", encoding="utf-8", newline="\n" + ) as f: self._write_entry_points(f) - license_files_to_add = [] + with (dist_info / "WHEEL").open("w", encoding="utf-8", newline="\n") as f: + self._write_wheel_file(f) + + with (dist_info / "METADATA").open("w", encoding="utf-8", newline="\n") as f: + self._write_metadata_file(f) + + license_files = set() for base in ("COPYING", "LICENSE"): - license_files_to_add.append(self._path / base) - license_files_to_add.extend(self._path.glob(base + ".*")) + license_files.add(self._path / base) + license_files.update(self._path.glob(base + ".*")) - license_files_to_add.extend(self._path.joinpath("LICENSES").glob("**/*")) + license_files.update(self._path.joinpath("LICENSES").glob("**/*")) - for path in set(license_files_to_add): - if path.is_file(): - relative_path = "%s/%s" % (self.dist_info, path.relative_to(self._path)) - self._add_file(wheel, path, relative_path) - else: - logger.debug("Skipping: {}".format(path.as_posix())) + for license_file in license_files: + if not license_file.is_file(): + logger.debug(f"Skipping: {license_file.as_posix()}") + continue - with self._write_to_zip(wheel, self.dist_info + "/WHEEL") as f: - self._write_wheel_file(f) + dest = dist_info / license_file.relative_to(self._path) + os.makedirs(dest.parent, exist_ok=True) + shutil.copy(license_file, dest) - with self._write_to_zip(wheel, self.dist_info + "/METADATA") as f: - self._write_metadata_file(f) + return dist_info - def _write_record(self, wheel): # type: (zipfile.ZipFile) -> None + def _write_record(self, wheel: zipfile.ZipFile) -> None: # Write a record of the files in the wheel with self._write_to_zip(wheel, self.dist_info + "/RECORD") as f: - record = StringIO() if not PY2 else BytesIO() + record = StringIO() csv_writer = csv.writer( record, @@ -249,41 +285,51 @@ def _write_record(self, wheel): # type: (zipfile.ZipFile) -> None lineterminator="\n", ) for path, hash, size in self._records: - csv_writer.writerow((path, "sha256={}".format(hash), size)) + csv_writer.writerow((path, f"sha256={hash}", size)) # RECORD itself is recorded with no hash or size csv_writer.writerow((self.dist_info + "/RECORD", "", "")) - f.write(decode(record.getvalue())) + f.write(record.getvalue()) + + def _copy_dist_info(self, wheel: zipfile.ZipFile, source: Path) -> None: + dist_info = Path(self.dist_info) + for file in source.glob("**/*"): + if not file.is_file(): + continue + + rel_path = file.relative_to(source) + target = dist_info / rel_path + self._add_file(wheel, file, target) @property - def dist_info(self): # type: () -> str + def dist_info(self) -> str: return self.dist_info_name(self._package.name, self._meta.version) @property - def wheel_filename(self): # type: () -> str - return "{}-{}-{}.whl".format( - escape_name(self._package.pretty_name), - escape_version(self._meta.version), - self.tag, - ) + def wheel_data_folder(self) -> str: + return f"{self._package.name}-{self._meta.version}.data" - def supports_python2(self): # type: () -> bool + @property + def wheel_filename(self) -> str: + name = distribution_name(self._package.name) + version = self._meta.version + return f"{name}-{version}-{self.tag}.whl" + + def supports_python2(self) -> bool: return self._package.python_constraint.allows_any( parse_constraint(">=2.0.0 <3.0.0") ) - def dist_info_name(self, distribution, version): # type: (str, str) -> str - escaped_name = escape_name(distribution) - escaped_version = escape_version(version) - - return "{}-{}.dist-info".format(escaped_name, escaped_version) + def dist_info_name(self, name: NormalizedName, version: str) -> str: + escaped_name = distribution_name(name) + return f"{escaped_name}-{version}.dist-info" @property - def tag(self): # type: () -> str + def tag(self) -> str: if self._package.build_script: - tag = next(sys_tags()) - tag = (tag.interpreter, tag.abi, tag.platform) + sys_tag = next(sys_tags()) + tag = (sys_tag.interpreter, sys_tag.abi, sys_tag.platform) else: platform = "any" if self.supports_python2(): @@ -296,8 +342,11 @@ def tag(self): # type: () -> str return "-".join(tag) def _add_file( - self, wheel, full_path, rel_path - ): # type: (zipfile.ZipFile, Union[Path, str], Union[Path, str]) -> None + self, + wheel: zipfile.ZipFile, + full_path: Path | str, + rel_path: Path | str, + ) -> None: full_path, rel_path = str(full_path), str(rel_path) if os.sep != "/": # We always want to have /-separated paths in the zip file and in @@ -332,8 +381,8 @@ def _add_file( @contextlib.contextmanager def _write_to_zip( - self, wheel, rel_path - ): # type: (zipfile.ZipFile, str) -> Iterator[StringIO] + self, wheel: zipfile.ZipFile, rel_path: str + ) -> Iterator[StringIO]: sio = StringIO() yield sio @@ -350,20 +399,20 @@ def _write_to_zip( wheel.writestr(zi, b, compress_type=zipfile.ZIP_DEFLATED) self._records.append((rel_path, hash_digest, len(b))) - def _write_entry_points(self, fp): # type: (TextIO) -> None + def _write_entry_points(self, fp: TextIO) -> None: """ Write entry_points.txt. """ entry_points = self.convert_entry_points() for group_name in sorted(entry_points): - fp.write("[{}]\n".format(group_name)) + fp.write(f"[{group_name}]\n") for ep in sorted(entry_points[group_name]): fp.write(ep.replace(" ", "") + "\n") fp.write("\n") - def _write_wheel_file(self, fp): # type: (TextIO) -> None + def _write_wheel_file(self, fp: TextIO) -> None: fp.write( wheel_file_template.format( version=__version__, @@ -372,8 +421,8 @@ def _write_wheel_file(self, fp): # type: (TextIO) -> None ) ) - def _write_metadata_file(self, fp): # type: (TextIO) -> None + def _write_metadata_file(self, fp: TextIO) -> None: """ Write out metadata in the 2.x format (email like) """ - fp.write(decode(self.get_metadata_content())) + fp.write(self.get_metadata_content()) diff --git a/conda_lock/_vendor/poetry/core/masonry/metadata.py b/conda_lock/_vendor/poetry/core/masonry/metadata.py index d84693514..ccb79194f 100644 --- a/conda_lock/_vendor/poetry/core/masonry/metadata.py +++ b/conda_lock/_vendor/poetry/core/masonry/metadata.py @@ -1,59 +1,65 @@ +from __future__ import annotations + from typing import TYPE_CHECKING -from conda_lock._vendor.poetry.core.utils.helpers import canonicalize_name -from conda_lock._vendor.poetry.core.utils.helpers import normalize_version -from conda_lock._vendor.poetry.core.version.helpers import format_python_constraint +from conda_lock._vendor.poetry.core.utils.helpers import readme_content_type if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.packages import Package # noqa + from packaging.utils import NormalizedName + from conda_lock._vendor.poetry.core.packages.package import Package -class Metadata: +class Metadata: metadata_version = "2.1" # version 1.0 - name = None - version = None - platforms = () - supported_platforms = () - summary = None - description = None - keywords = None - home_page = None - download_url = None - author = None - author_email = None - license = None + name: NormalizedName | None = None + version: str + platforms: tuple[str, ...] = () + supported_platforms: tuple[str, ...] = () + summary: str | None = None + description: str | None = None + keywords: str | None = None + home_page: str | None = None + download_url: str | None = None + author: str | None = None + author_email: str | None = None + license: str | None = None # version 1.1 - classifiers = () - requires = () - provides = () - obsoletes = () + classifiers: tuple[str, ...] = () + requires: tuple[str, ...] = () + provides: tuple[str, ...] = () + obsoletes: tuple[str, ...] = () # version 1.2 - maintainer = None - maintainer_email = None - requires_python = None - requires_external = () - requires_dist = [] - provides_dist = () - obsoletes_dist = () - project_urls = () + maintainer: str | None = None + maintainer_email: str | None = None + requires_python: str | None = None + requires_external: tuple[str, ...] = () + requires_dist: list[str] = [] + provides_dist: tuple[str, ...] = () + obsoletes_dist: tuple[str, ...] = () + project_urls: tuple[str, ...] = () # Version 2.1 - description_content_type = None - provides_extra = [] + description_content_type: str | None = None + provides_extra: list[str] = [] @classmethod - def from_package(cls, package): # type: ("Package") -> Metadata + def from_package(cls, package: Package) -> Metadata: + from conda_lock._vendor.poetry.core.version.helpers import format_python_constraint + meta = cls() - meta.name = canonicalize_name(package.name) - meta.version = normalize_version(package.version.text) + meta.name = package.name + meta.version = package.version.to_string() meta.summary = package.description - if package.readme: - with package.readme.open(encoding="utf-8") as f: - meta.description = f.read() + if package.readmes: + descriptions = [] + for readme in package.readmes: + with readme.open(encoding="utf-8") as f: + descriptions.append(f.read()) + meta.description = "\n".join(descriptions) meta.keywords = ",".join(package.keywords) meta.home_page = package.homepage or package.repository_url @@ -63,7 +69,7 @@ def from_package(cls, package): # type: ("Package") -> Metadata if package.license: meta.license = package.license.id - meta.classifiers = package.all_classifiers + meta.classifiers = tuple(package.all_classifiers) # Version 1.2 meta.maintainer = package.maintainer_name @@ -76,21 +82,16 @@ def from_package(cls, package): # type: ("Package") -> Metadata meta.requires_dist = [d.to_pep_508() for d in package.requires] # Version 2.1 - if package.readme: - if package.readme.suffix == ".rst": - meta.description_content_type = "text/x-rst" - elif package.readme.suffix in [".md", ".markdown"]: - meta.description_content_type = "text/markdown" - else: - meta.description_content_type = "text/plain" + if package.readmes: + meta.description_content_type = readme_content_type(package.readmes[0]) - meta.provides_extra = [e for e in package.extras] + meta.provides_extra = list(package.extras) if package.urls: for name, url in package.urls.items(): if name == "Homepage" and meta.home_page == url: continue - meta.project_urls += ("{}, {}".format(name, url),) + meta.project_urls += (f"{name}, {url}",) return meta diff --git a/conda_lock/_vendor/poetry/core/masonry/utils/helpers.py b/conda_lock/_vendor/poetry/core/masonry/utils/helpers.py index 3a515f425..cad1b4ea4 100644 --- a/conda_lock/_vendor/poetry/core/masonry/utils/helpers.py +++ b/conda_lock/_vendor/poetry/core/masonry/utils/helpers.py @@ -1,7 +1,21 @@ +from __future__ import annotations + import re +import warnings + +from typing import TYPE_CHECKING +from typing import NewType +from typing import cast + + +if TYPE_CHECKING: + from packaging.utils import NormalizedName + +DistributionName = NewType("DistributionName", str) -def normalize_file_permissions(st_mode): # type: (int) -> int + +def normalize_file_permissions(st_mode: int) -> int: """ Normalizes the permission bits in the st_mode field from stat to 644/755 @@ -17,15 +31,53 @@ def normalize_file_permissions(st_mode): # type: (int) -> int return new_mode -def escape_version(version): # type: (str) -> str +def escape_version(version: str) -> str: """ Escaped version in wheel filename. Doesn't exactly follow the escaping specification in :pep:`427#escaping-and-unicode` because this conflicts with :pep:`440#local-version-identifiers`. """ + warnings.warn( + "escape_version() is deprecated. Use Version.parse().to_string() instead.", + DeprecationWarning, + stacklevel=2, + ) return re.sub(r"[^\w\d.+]+", "_", version, flags=re.UNICODE) -def escape_name(name): # type: (str) -> str - """Escaped wheel name as specified in :pep:`427#escaping-and-unicode`.""" - return re.sub(r"[^\w\d.]+", "_", name, flags=re.UNICODE) +def escape_name(name: str) -> str: + """ + Escaped wheel name as specified in https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode. + This function should only be used for the generation of artifact names, and not to normalize or filter existing artifact names. + """ + warnings.warn( + "escape_name() is deprecated. Use packaging.utils.canonicalize_name() and" + " distribution_name() instead.", + DeprecationWarning, + stacklevel=2, + ) + return re.sub(r"[-_.]+", "_", name, flags=re.UNICODE).lower() + + +def distribution_name(name: NormalizedName) -> DistributionName: + """ + A normalized name, but with "-" replaced by "_". This is used in various places: + + https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode + + In distribution names ... This is equivalent to PEP 503 normalisation followed by + replacing - with _. + + https://packaging.python.org/en/latest/specifications/source-distribution-format/#source-distribution-file-name + + ... {name} is normalised according to the same rules as for binary distributions + + https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-dist-info-directory + + This directory is named as {name}-{version}.dist-info, with name and version fields + corresponding to Core metadata specifications. Both fields must be normalized + (see PEP 503 and PEP 440 for the definition of normalization for each field + respectively), and replace dash (-) characters with underscore (_) characters ... + """ + distribution_name = name.replace("-", "_") + return cast("DistributionName", distribution_name) diff --git a/conda_lock/_vendor/poetry/core/masonry/utils/include.py b/conda_lock/_vendor/poetry/core/masonry/utils/include.py index af40c1b80..f183aa6c8 100644 --- a/conda_lock/_vendor/poetry/core/masonry/utils/include.py +++ b/conda_lock/_vendor/poetry/core/masonry/utils/include.py @@ -1,10 +1,13 @@ -from typing import List -from typing import Optional +from __future__ import annotations -from conda_lock._vendor.poetry.core.utils._compat import Path +from typing import TYPE_CHECKING -class Include(object): +if TYPE_CHECKING: + from pathlib import Path + + +class Include: """ Represents an "include" entry. @@ -19,32 +22,30 @@ class Include(object): """ def __init__( - self, base, include, formats=None - ): # type: (Path, str, Optional[List[str]]) -> None + self, base: Path, include: str, formats: list[str] | None = None + ) -> None: self._base = base self._include = str(include) self._formats = formats - self._elements = sorted( - list(self._base.glob(str(self._include))) - ) # type: List[Path] + self._elements: list[Path] = sorted(self._base.glob(str(self._include))) @property - def base(self): # type: () -> Path + def base(self) -> Path: return self._base @property - def elements(self): # type: () -> List[Path] + def elements(self) -> list[Path]: return self._elements @property - def formats(self): # type: () -> Optional[List[str]] + def formats(self) -> list[str] | None: return self._formats - def is_empty(self): # type: () -> bool + def is_empty(self) -> bool: return len(self._elements) == 0 - def refresh(self): # type: () -> Include - self._elements = sorted(list(self._base.glob(self._include))) + def refresh(self) -> Include: + self._elements = sorted(self._base.glob(self._include)) return self diff --git a/conda_lock/_vendor/poetry/core/masonry/utils/module.py b/conda_lock/_vendor/poetry/core/masonry/utils/module.py index c8575f6bd..b12679d07 100644 --- a/conda_lock/_vendor/poetry/core/masonry/utils/module.py +++ b/conda_lock/_vendor/poetry/core/masonry/utils/module.py @@ -1,29 +1,35 @@ +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils.helpers import module_name -from .include import Include -from .package_include import PackageInclude +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.masonry.utils.include import Include class ModuleOrPackageNotFound(ValueError): - pass class Module: def __init__( - self, name, directory=".", packages=None, includes=None - ): # type: (str, str, Optional[List[Dict[str, Any]]], Optional[List[Dict[str, Any]]]) -> None + self, + name: str, + directory: str = ".", + packages: list[dict[str, Any]] | None = None, + includes: list[dict[str, Any]] | None = None, + ) -> None: + from conda_lock._vendor.poetry.core.masonry.utils.include import Include + from conda_lock._vendor.poetry.core.masonry.utils.package_include import PackageInclude + from conda_lock._vendor.poetry.core.utils.helpers import module_name + self._name = module_name(name) self._in_src = False self._is_package = False self._path = Path(directory) - self._includes = [] + self._includes: list[Include] = [] packages = packages or [] includes = includes or [] @@ -32,7 +38,7 @@ def __init__( pkg_dir = Path(directory, self._name) py_file = Path(directory, self._name + ".py") if pkg_dir.is_dir() and py_file.is_file(): - raise ValueError("Both {} and {} exist".format(pkg_dir, py_file)) + raise ValueError(f"Both {pkg_dir} and {py_file} exist") elif pkg_dir.is_dir(): packages = [{"include": str(pkg_dir.relative_to(self._path))}] elif py_file.is_file(): @@ -44,7 +50,7 @@ def __init__( src_py_file = src / (self._name + ".py") if src_pkg_dir.is_dir() and src_py_file.is_file(): - raise ValueError("Both {} and {} exist".format(pkg_dir, py_file)) + raise ValueError(f"Both {pkg_dir} and {py_file} exist") elif src_pkg_dir.is_dir(): packages = [ { @@ -61,7 +67,7 @@ def __init__( ] else: raise ModuleOrPackageNotFound( - "No file/folder found for package {}".format(name) + f"No file/folder found for package {name}" ) for package in packages: @@ -84,26 +90,26 @@ def __init__( ) @property - def name(self): # type: () -> str + def name(self) -> str: return self._name @property - def path(self): # type: () -> Path + def path(self) -> Path: return self._path @property - def file(self): # type: () -> Path + def file(self) -> Path: if self._is_package: return self._path / "__init__.py" else: return self._path @property - def includes(self): # type: () -> List + def includes(self) -> list[Include]: return self._includes - def is_package(self): # type: () -> bool + def is_package(self) -> bool: return self._is_package - def is_in_src(self): # type: () -> bool + def is_in_src(self) -> bool: return self._in_src diff --git a/conda_lock/_vendor/poetry/core/masonry/utils/package_include.py b/conda_lock/_vendor/poetry/core/masonry/utils/package_include.py index 4758694b9..a3d251093 100644 --- a/conda_lock/_vendor/poetry/core/masonry/utils/package_include.py +++ b/conda_lock/_vendor/poetry/core/masonry/utils/package_include.py @@ -1,16 +1,23 @@ -from typing import List -from typing import Optional +from __future__ import annotations -from conda_lock._vendor.poetry.core.utils._compat import Path +from typing import TYPE_CHECKING -from .include import Include +from conda_lock._vendor.poetry.core.masonry.utils.include import Include + + +if TYPE_CHECKING: + from pathlib import Path class PackageInclude(Include): def __init__( - self, base, include, formats=None, source=None - ): # type: (Path, str, Optional[List[str]], Optional[str]) -> None - self._package = None + self, + base: Path, + include: str, + formats: list[str] | None = None, + source: str | None = None, + ) -> None: + self._package: str self._is_package = False self._is_module = False self._source = source @@ -18,47 +25,46 @@ def __init__( if source is not None: base = base / source - super(PackageInclude, self).__init__(base, include, formats=formats) + super().__init__(base, include, formats=formats) self.check_elements() @property - def package(self): # type: () -> str + def package(self) -> str: return self._package @property - def source(self): # type: () -> Optional[str] + def source(self) -> str | None: return self._source - def is_package(self): # type: () -> bool + def is_package(self) -> bool: return self._is_package - def is_module(self): # type: () -> bool + def is_module(self) -> bool: return self._is_module - def refresh(self): # type: () -> PackageInclude - super(PackageInclude, self).refresh() + def refresh(self) -> PackageInclude: + super().refresh() return self.check_elements() - def is_stub_only(self): # type: () -> bool + def is_stub_only(self) -> bool: # returns `True` if this a PEP 561 stub-only package, # see [PEP 561](https://www.python.org/dev/peps/pep-0561/#stub-only-packages) - return self.package.endswith("-stubs") and all( - el.suffix == ".pyi" - or (el.parent.name == self.package and el.name == "py.typed") + return (self.package or "").endswith("-stubs") and all( + el.suffix == ".pyi" or el.name == "py.typed" for el in self.elements if el.is_file() ) - def has_modules(self): # type: () -> bool + def has_modules(self) -> bool: # Packages no longer need an __init__.py in python3, but there must # at least be one .py file for it to be considered a package return any(element.suffix == ".py" for element in self.elements) - def check_elements(self): # type: () -> PackageInclude + def check_elements(self) -> PackageInclude: if not self._elements: raise ValueError( - "{} does not contain any element".format(self._base / self._include) + f"{self._base / self._include} does not contain any element" ) root = self._elements[0] @@ -68,16 +74,16 @@ def check_elements(self): # type: () -> PackageInclude self._package = root.parent.name if not self.is_stub_only() and not self.has_modules(): - raise ValueError("{} is not a package.".format(root.name)) + raise ValueError(f"{root.name} is not a package.") else: if root.is_dir(): # If it's a directory, we include everything inside it self._package = root.name - self._elements = sorted(list(root.glob("**/*"))) # type: List[Path] + self._elements: list[Path] = sorted(root.glob("**/*")) if not self.is_stub_only() and not self.has_modules(): - raise ValueError("{} is not a package.".format(root.name)) + raise ValueError(f"{root.name} is not a package.") self._is_package = True else: diff --git a/conda_lock/_vendor/poetry/core/packages/__init__.py b/conda_lock/_vendor/poetry/core/packages/__init__.py index bb19288c5..e69de29bb 100644 --- a/conda_lock/_vendor/poetry/core/packages/__init__.py +++ b/conda_lock/_vendor/poetry/core/packages/__init__.py @@ -1,207 +0,0 @@ -import os -import re - -from typing import List -from typing import Optional -from typing import Union - -from conda_lock._vendor.poetry.core.semver import parse_constraint -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils.patterns import wheel_file_re -from conda_lock._vendor.poetry.core.version.requirements import Requirement - -from .dependency import Dependency -from .directory_dependency import DirectoryDependency -from .file_dependency import FileDependency -from .package import Package -from .project_package import ProjectPackage -from .url_dependency import URLDependency -from .utils.link import Link -from .utils.utils import convert_markers -from .utils.utils import group_markers -from .utils.utils import is_archive_file -from .utils.utils import is_installable_dir -from .utils.utils import is_url -from .utils.utils import path_to_url -from .utils.utils import strip_extras -from .utils.utils import url_to_path -from .vcs_dependency import VCSDependency - - -def _make_file_or_dir_dep( - name, # type: str - path, # type: Path - base=None, # type: Optional[Path] - extras=None, # type: Optional[List[str]] -): # type: (...) -> Optional[Union[FileDependency, DirectoryDependency]] - """ - Helper function to create a file or directoru dependency with the given arguments. If - path is not a file or directory that exists, `None` is returned. - """ - _path = path - if not path.is_absolute() and base: - # a base path was specified, so we should respect that - _path = Path(base) / path - - if _path.is_file(): - return FileDependency(name, path, base=base, extras=extras) - elif _path.is_dir(): - return DirectoryDependency(name, path, base=base, extras=extras) - - return None - - -def dependency_from_pep_508( - name, relative_to=None -): # type: (str, Optional[Path]) -> Dependency - """ - Resolve a PEP-508 requirement string to a `Dependency` instance. If a `relative_to` - path is specified, this is used as the base directory if the identified dependency is - of file or directory type. - """ - from conda_lock._vendor.poetry.core.vcs.git import ParsedUrl - - # Removing comments - parts = name.split("#", 1) - name = parts[0].strip() - if len(parts) > 1: - rest = parts[1] - if " ;" in rest: - name += " ;" + rest.split(" ;", 1)[1] - - req = Requirement(name) - - if req.marker: - markers = convert_markers(req.marker) - else: - markers = {} - - name = req.name - path = os.path.normpath(os.path.abspath(name)) - link = None - - if is_url(name): - link = Link(name) - elif req.url: - link = Link(req.url) - else: - p, extras = strip_extras(path) - if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")): - - if not is_installable_dir(p): - raise ValueError( - "Directory {!r} is not installable. File 'setup.py' " - "not found.".format(name) - ) - link = Link(path_to_url(p)) - elif is_archive_file(p): - link = Link(path_to_url(p)) - - # it's a local file, dir, or url - if link: - is_file_uri = link.scheme == "file" - is_relative_uri = is_file_uri and re.search(r"\.\./", link.url) - - # Handle relative file URLs - if is_file_uri and is_relative_uri: - path = Path(link.path) - if relative_to: - path = relative_to / path - link = Link(path_to_url(path)) - - # wheel file - version = None - if link.is_wheel: - m = wheel_file_re.match(link.filename) - if not m: - raise ValueError("Invalid wheel name: {}".format(link.filename)) - name = m.group("name") - version = m.group("ver") - - name = req.name or link.egg_fragment - dep = None - - if link.scheme.startswith("git+"): - url = ParsedUrl.parse(link.url) - dep = VCSDependency(name, "git", url.url, rev=url.rev, extras=req.extras) - elif link.scheme == "git": - dep = VCSDependency( - name, "git", link.url_without_fragment, extras=req.extras - ) - elif link.scheme in ["http", "https"]: - dep = URLDependency(name, link.url) - elif is_file_uri: - # handle RFC 8089 references - path = url_to_path(req.url) - dep = _make_file_or_dir_dep( - name=name, path=path, base=relative_to, extras=req.extras - ) - else: - try: - # this is a local path not using the file URI scheme - dep = _make_file_or_dir_dep( - name=name, path=Path(req.url), base=relative_to, extras=req.extras, - ) - except ValueError: - pass - - if dep is None: - dep = Dependency(name, version or "*", extras=req.extras) - - if version: - dep._constraint = parse_constraint(version) - else: - if req.pretty_constraint: - constraint = req.constraint - else: - constraint = "*" - - dep = Dependency(name, constraint, extras=req.extras) - - if "extra" in markers: - # If we have extras, the dependency is optional - dep.deactivate() - - for or_ in markers["extra"]: - for _, extra in or_: - dep.in_extras.append(extra) - - if "python_version" in markers: - ors = [] - for or_ in markers["python_version"]: - ands = [] - for op, version in or_: - # Expand python version - if op == "==" and "*" not in version: - version = "~" + version - op = "" - elif op == "!=": - version += ".*" - 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)) - - dep.python_versions = " || ".join(ors) - - if req.marker: - dep.marker = req.marker - - return dep diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/__init__.py b/conda_lock/_vendor/poetry/core/packages/constraints/__init__.py index 33acb85a7..7c0a12395 100644 --- a/conda_lock/_vendor/poetry/core/packages/constraints/__init__.py +++ b/conda_lock/_vendor/poetry/core/packages/constraints/__init__.py @@ -1,66 +1,32 @@ -import re - -from typing import Union - -from .any_constraint import AnyConstraint -from .base_constraint import BaseConstraint -from .constraint import Constraint -from .empty_constraint import EmptyConstraint -from .multi_constraint import MultiConstraint -from .union_constraint import UnionConstraint - - -BASIC_CONSTRAINT = re.compile(r"^(!?==?)?\s*([^\s]+?)\s*$") -ConstraintTypes = Union[ - AnyConstraint, Constraint, UnionConstraint, EmptyConstraint, MultiConstraint +from __future__ import annotations + +import warnings + +from conda_lock._vendor.poetry.core.constraints.generic import AnyConstraint +from conda_lock._vendor.poetry.core.constraints.generic import BaseConstraint +from conda_lock._vendor.poetry.core.constraints.generic import Constraint +from conda_lock._vendor.poetry.core.constraints.generic import EmptyConstraint +from conda_lock._vendor.poetry.core.constraints.generic import MultiConstraint +from conda_lock._vendor.poetry.core.constraints.generic import UnionConstraint +from conda_lock._vendor.poetry.core.constraints.generic import parse_constraint +from conda_lock._vendor.poetry.core.constraints.generic.parser import parse_single_constraint + + +warnings.warn( + "poetry.core.packages.constraints is deprecated." + " Use poetry.core.constraints.generic instead.", + DeprecationWarning, + stacklevel=2, +) + + +__all__ = [ + "AnyConstraint", + "BaseConstraint", + "Constraint", + "EmptyConstraint", + "MultiConstraint", + "UnionConstraint", + "parse_constraint", + "parse_single_constraint", ] - - -def parse_constraint( - constraints, -): # type: (str) -> Union[AnyConstraint, UnionConstraint, Constraint] - if constraints == "*": - return AnyConstraint() - - or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip()) - or_groups = [] - for constraints in or_constraints: - and_constraints = re.split( - r"(?< ,]) *(? 1: - for constraint in and_constraints: - constraint_objects.append(parse_single_constraint(constraint)) - else: - constraint_objects.append(parse_single_constraint(and_constraints[0])) - - if len(constraint_objects) == 1: - constraint = constraint_objects[0] - else: - constraint = constraint_objects[0] - for next_constraint in constraint_objects[1:]: - constraint = constraint.intersect(next_constraint) - - or_groups.append(constraint) - - if len(or_groups) == 1: - return or_groups[0] - else: - return UnionConstraint(*or_groups) - - -def parse_single_constraint(constraint): # type: (str) -> Constraint - # Basic comparator - m = BASIC_CONSTRAINT.match(constraint) - if m: - op = m.group(1) - if op is None: - op = "==" - - version = m.group(2).strip() - - return Constraint(version, op) - - raise ValueError("Could not parse version constraint: {}".format(constraint)) diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/any_constraint.py b/conda_lock/_vendor/poetry/core/packages/constraints/any_constraint.py deleted file mode 100644 index 88945a119..000000000 --- a/conda_lock/_vendor/poetry/core/packages/constraints/any_constraint.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import TYPE_CHECKING - -from .base_constraint import BaseConstraint -from .empty_constraint import EmptyConstraint - - -if TYPE_CHECKING: - from . import ConstraintTypes # noqa - - -class AnyConstraint(BaseConstraint): - def allows(self, other): # type: ("ConstraintTypes") -> bool - return True - - def allows_all(self, other): # type: ("ConstraintTypes") -> bool - return True - - def allows_any(self, other): # type: ("ConstraintTypes") -> bool - return True - - def difference(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - if other.is_any(): - return EmptyConstraint() - - return other - - def intersect(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - return other - - def union(self, other): # type: ("ConstraintTypes") -> AnyConstraint - return AnyConstraint() - - def is_any(self): # type: () -> bool - return True - - def is_empty(self): # type: () -> bool - return False - - def __str__(self): # type: () -> str - return "*" - - def __eq__(self, other): # type: ("ConstraintTypes") -> bool - return other.is_any() diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/base_constraint.py b/conda_lock/_vendor/poetry/core/packages/constraints/base_constraint.py deleted file mode 100644 index 0db9aff42..000000000 --- a/conda_lock/_vendor/poetry/core/packages/constraints/base_constraint.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from . import ConstraintTypes # noqa - - -class BaseConstraint(object): - def allows(self, other): # type: ("ConstraintTypes") -> bool - raise NotImplementedError - - def allows_all(self, other): # type: ("ConstraintTypes") -> bool - raise NotImplementedError() - - def allows_any(self, other): # type: ("ConstraintTypes") -> bool - raise NotImplementedError() - - def difference(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - raise NotImplementedError() - - def intersect(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - raise NotImplementedError() - - def union(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - raise NotImplementedError() - - def is_any(self): # type: () -> bool - return False - - def is_empty(self): # type: () -> bool - return False - - def __repr__(self): # type: () -> str - return "<{} {}>".format(self.__class__.__name__, str(self)) - - def __eq__(self, other): # type: ("ConstraintTypes") -> bool - raise NotImplementedError() diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/empty_constraint.py b/conda_lock/_vendor/poetry/core/packages/constraints/empty_constraint.py deleted file mode 100644 index 4db043def..000000000 --- a/conda_lock/_vendor/poetry/core/packages/constraints/empty_constraint.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import TYPE_CHECKING - -from .base_constraint import BaseConstraint - - -if TYPE_CHECKING: - from . import ConstraintTypes # noqa - - -class EmptyConstraint(BaseConstraint): - - pretty_string = None - - def matches(self, _): # type: ("ConstraintTypes") -> bool - return True - - def is_empty(self): # type: () -> bool - return True - - def allows(self, other): # type: ("ConstraintTypes") -> bool - return False - - def allows_all(self, other): # type: ("ConstraintTypes") -> bool - return True - - def allows_any(self, other): # type: ("ConstraintTypes") -> bool - return True - - def intersect(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - return other - - def difference(self, other): # type: ("ConstraintTypes") -> None - return - - def __eq__(self, other): # type: ("ConstraintTypes") -> bool - return other.is_empty() - - def __str__(self): # type: () -> str - return "" diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/multi_constraint.py b/conda_lock/_vendor/poetry/core/packages/constraints/multi_constraint.py deleted file mode 100644 index 33fc9e4a5..000000000 --- a/conda_lock/_vendor/poetry/core/packages/constraints/multi_constraint.py +++ /dev/null @@ -1,100 +0,0 @@ -from typing import TYPE_CHECKING -from typing import Any -from typing import Tuple - -from .base_constraint import BaseConstraint -from .constraint import Constraint - - -if TYPE_CHECKING: - from . import ConstraintTypes # noqa - - -class MultiConstraint(BaseConstraint): - def __init__(self, *constraints): # type: (*Constraint) -> None - if any(c.operator == "==" for c in constraints): - raise ValueError( - "A multi-constraint can only be comprised of negative constraints" - ) - - self._constraints = constraints - - @property - def constraints(self): # type: () -> Tuple[Constraint] - return self._constraints - - def allows(self, other): # type: ("ConstraintTypes") -> bool - for constraint in self._constraints: - if not constraint.allows(other): - return False - - return True - - def allows_all(self, other): # type: ("ConstraintTypes") -> bool - if other.is_any(): - return False - - if other.is_empty(): - return True - - if isinstance(other, Constraint): - return self.allows(other) - - our_constraints = iter(self._constraints) - their_constraints = iter(other.constraints) - our_constraint = next(our_constraints, None) - their_constraint = next(their_constraints, None) - - while our_constraint and their_constraint: - if our_constraint.allows_all(their_constraint): - their_constraint = next(their_constraints, None) - else: - our_constraint = next(our_constraints, None) - - return their_constraint is None - - def allows_any(self, other): # type: ("ConstraintTypes") -> bool - if other.is_any(): - return True - - if other.is_empty(): - return True - - if isinstance(other, Constraint): - return self.allows(other) - - if isinstance(other, MultiConstraint): - for c1 in self.constraints: - for c2 in other.constraints: - if c1.allows(c2): - return True - - return False - - def intersect(self, other): # type: (Constraint) -> MultiConstraint - if isinstance(other, Constraint): - constraints = self._constraints - if other not in constraints: - constraints += (other,) - else: - constraints = (other,) - - if len(constraints) == 1: - return constraints[0] - - return MultiConstraint(*constraints) - - def __eq__(self, other): # type: (Any) -> bool - if not isinstance(other, MultiConstraint): - return False - - return sorted( - self._constraints, key=lambda c: (c.operator, c.version) - ) == sorted(other.constraints, key=lambda c: (c.operator, c.version)) - - def __str__(self): # type: () -> str - constraints = [] - for constraint in self._constraints: - constraints.append(str(constraint)) - - return "{}".format(", ").join(constraints) diff --git a/conda_lock/_vendor/poetry/core/packages/constraints/union_constraint.py b/conda_lock/_vendor/poetry/core/packages/constraints/union_constraint.py deleted file mode 100644 index ec0330c2d..000000000 --- a/conda_lock/_vendor/poetry/core/packages/constraints/union_constraint.py +++ /dev/null @@ -1,124 +0,0 @@ -from typing import TYPE_CHECKING -from typing import Tuple -from typing import Union - -from .base_constraint import BaseConstraint -from .constraint import Constraint -from .empty_constraint import EmptyConstraint -from .multi_constraint import MultiConstraint - - -if TYPE_CHECKING: - from . import ConstraintTypes # noqa - - -class UnionConstraint(BaseConstraint): - def __init__(self, *constraints): # type: (*Constraint) -> None - self._constraints = constraints - - @property - def constraints(self): # type: () -> Tuple[Constraint] - return self._constraints - - def allows( - self, other - ): # type: (Union[Constraint, MultiConstraint, UnionConstraint]) -> bool - for constraint in self._constraints: - if constraint.allows(other): - return True - - return False - - def allows_any(self, other): # type: ("ConstraintTypes") -> bool - if other.is_empty(): - return False - - if other.is_any(): - return True - - if isinstance(other, Constraint): - constraints = [other] - else: - constraints = other.constraints - - for our_constraint in self._constraints: - for their_constraint in constraints: - if our_constraint.allows_any(their_constraint): - return True - - return False - - def allows_all(self, other): # type: ("ConstraintTypes") -> bool - if other.is_any(): - return False - - if other.is_empty(): - return True - - if isinstance(other, Constraint): - constraints = [other] - else: - constraints = other.constraints - - our_constraints = iter(self._constraints) - their_constraints = iter(constraints) - our_constraint = next(our_constraints, None) - their_constraint = next(their_constraints, None) - - while our_constraint and their_constraint: - if our_constraint.allows_all(their_constraint): - their_constraint = next(their_constraints, None) - else: - our_constraint = next(our_constraints, None) - - return their_constraint is None - - def intersect(self, other): # type: ("ConstraintTypes") -> "ConstraintTypes" - if other.is_any(): - return self - - if other.is_empty(): - return other - - if isinstance(other, Constraint): - if self.allows(other): - return other - - return EmptyConstraint() - - new_constraints = [] - for our_constraint in self._constraints: - for their_constraint in other.constraints: - intersection = our_constraint.intersect(their_constraint) - - if not intersection.is_empty() and intersection not in new_constraints: - new_constraints.append(intersection) - - if not new_constraints: - return EmptyConstraint() - - return UnionConstraint(*new_constraints) - - def union(self, other): # type: (Constraint) -> UnionConstraint - if isinstance(other, Constraint): - constraints = self._constraints - if other not in self._constraints: - constraints += (other,) - - return UnionConstraint(*constraints) - - def __eq__(self, other): # type: ("ConstraintTypes") -> bool - - if not isinstance(other, UnionConstraint): - return False - - return sorted( - self._constraints, key=lambda c: (c.operator, c.version) - ) == sorted(other.constraints, key=lambda c: (c.operator, c.version)) - - def __str__(self): # type: () -> str - constraints = [] - for constraint in self._constraints: - constraints.append(str(constraint)) - - return "{}".format(" || ").join(constraints) diff --git a/conda_lock/_vendor/poetry/core/packages/dependency.py b/conda_lock/_vendor/poetry/core/packages/dependency.py index 2e544c16b..8d5f96585 100644 --- a/conda_lock/_vendor/poetry/core/packages/dependency.py +++ b/conda_lock/_vendor/poetry/core/packages/dependency.py @@ -1,228 +1,290 @@ +from __future__ import annotations + +import os +import re +import warnings + +from contextlib import suppress +from pathlib import Path from typing import TYPE_CHECKING -from typing import Any -from typing import FrozenSet -from typing import List -from typing import Optional -from typing import Union - -from conda_lock._vendor.poetry.core.semver import Version -from conda_lock._vendor.poetry.core.semver import VersionConstraint -from conda_lock._vendor.poetry.core.semver import VersionRange -from conda_lock._vendor.poetry.core.semver import VersionUnion -from conda_lock._vendor.poetry.core.semver import parse_constraint -from conda_lock._vendor.poetry.core.version.markers import AnyMarker +from typing import Iterable +from typing import TypeVar + +from packaging.utils import canonicalize_name + +from conda_lock._vendor.poetry.core.constraints.generic import parse_constraint as parse_generic_constraint +from conda_lock._vendor.poetry.core.constraints.version import VersionRangeConstraint +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint +from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP +from conda_lock._vendor.poetry.core.packages.specification import PackageSpecification +from conda_lock._vendor.poetry.core.packages.utils.utils import contains_group_without_marker +from conda_lock._vendor.poetry.core.packages.utils.utils import create_nested_marker +from conda_lock._vendor.poetry.core.packages.utils.utils import normalize_python_version_markers from conda_lock._vendor.poetry.core.version.markers import parse_marker -from .constraints import parse_constraint as parse_generic_constraint -from .constraints.constraint import Constraint -from .constraints.multi_constraint import MultiConstraint -from .constraints.union_constraint import UnionConstraint -from .specification import PackageSpecification -from .utils.utils import convert_markers - if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.version.markers import BaseMarker # noqa - from conda_lock._vendor.poetry.core.packages import Package # noqa - from conda_lock._vendor.poetry.core.version.markers import VersionTypes # noqa + from packaging.utils import NormalizedName - from .constraints import BaseConstraint # noqa + from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint + from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency + from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency + from conda_lock._vendor.poetry.core.version.markers import BaseMarker + + T = TypeVar("T", bound="Dependency") class Dependency(PackageSpecification): def __init__( self, - name, # type: str - constraint, # type: Union[str, VersionConstraint] - optional=False, # type: bool - category="main", # type: str - allows_prereleases=False, # type: bool - extras=None, # type: Union[List[str], FrozenSet[str]] - source_type=None, # type: Optional[str] - source_url=None, # type: Optional[str] - source_reference=None, # type: Optional[str] - source_resolved_reference=None, # type: Optional[str] - ): - super(Dependency, self).__init__( + name: str, + constraint: str | VersionConstraint, + optional: bool = False, + groups: Iterable[str] | None = None, + allows_prereleases: bool = False, + extras: Iterable[str] | None = None, + source_type: str | None = None, + source_url: str | None = None, + source_reference: str | None = None, + source_resolved_reference: str | None = None, + source_subdirectory: str | None = None, + ) -> None: + from conda_lock._vendor.poetry.core.version.markers import AnyMarker + + super().__init__( name, source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, + source_subdirectory=source_subdirectory, features=extras, ) - self._constraint = None - self.set_constraint(constraint=constraint) + self._constraint: VersionConstraint + self._pretty_constraint: str + self.constraint = constraint # type: ignore[assignment] - self._pretty_constraint = str(constraint) self._optional = optional - self._category = category - if isinstance(self._constraint, VersionRange) and self._constraint.min: + if not groups: + groups = [MAIN_GROUP] + + self._groups = frozenset(groups) + + if ( + isinstance(self._constraint, VersionRangeConstraint) + and self._constraint.min + ): allows_prereleases = ( - allows_prereleases or self._constraint.min.is_prerelease() + allows_prereleases or self._constraint.min.is_unstable() ) self._allows_prereleases = allows_prereleases self._python_versions = "*" self._python_constraint = parse_constraint("*") - self._transitive_python_versions = None - self._transitive_python_constraint = None - self._transitive_marker = None - self._extras = frozenset(extras or []) + self._transitive_python_versions: str | None = None + self._transitive_python_constraint: VersionConstraint | None = None + self._transitive_marker: BaseMarker | None = None - self._in_extras = [] + self._in_extras: list[NormalizedName] = [] self._activated = not self._optional self.is_root = False - self.marker = AnyMarker() - self.source_name = None + self._marker: BaseMarker = AnyMarker() + self.source_name: str | None = None @property - def name(self): # type: () -> str + def name(self) -> NormalizedName: return self._name @property - def constraint(self): # type: () -> "VersionTypes" + def constraint(self) -> VersionConstraint: return self._constraint - def set_constraint(self, constraint): # type: (Union[str, "VersionTypes"]) -> None - try: - if not isinstance(constraint, VersionConstraint): - self._constraint = parse_constraint(constraint) - else: - self._constraint = constraint - except ValueError: - self._constraint = parse_constraint("*") + @constraint.setter + def constraint(self, constraint: str | VersionConstraint) -> None: + if isinstance(constraint, str): + self._constraint = parse_constraint(constraint) + else: + self._constraint = constraint + + self._pretty_constraint = str(constraint) + + def set_constraint(self, constraint: str | VersionConstraint) -> None: + warnings.warn( + "Calling method 'set_constraint' is deprecated and will be removed. " + "It has been replaced by the property 'constraint' for consistency.", + DeprecationWarning, + stacklevel=2, + ) + self.constraint = constraint # type: ignore[assignment] @property - def pretty_constraint(self): # type: () -> str + def pretty_constraint(self) -> str: return self._pretty_constraint @property - def pretty_name(self): # type: () -> str + def pretty_name(self) -> str: return self._pretty_name @property - def category(self): # type: () -> str - return self._category + def groups(self) -> frozenset[str]: + return self._groups @property - def python_versions(self): # type: () -> str + def python_versions(self) -> str: return self._python_versions @python_versions.setter - def python_versions(self, value): # type: (str) -> None + def python_versions(self, value: str) -> None: self._python_versions = value self._python_constraint = parse_constraint(value) if not self._python_constraint.is_any(): - self.marker = self.marker.intersect( + self._marker = self._marker.intersect( parse_marker( - self._create_nested_marker( - "python_version", self._python_constraint - ) + create_nested_marker("python_version", self._python_constraint) ) ) @property - def transitive_python_versions(self): # type: () -> str + def transitive_python_versions(self) -> str: if self._transitive_python_versions is None: return self._python_versions return self._transitive_python_versions @transitive_python_versions.setter - def transitive_python_versions(self, value): # type: (str) -> None + def transitive_python_versions(self, value: str) -> None: self._transitive_python_versions = value self._transitive_python_constraint = parse_constraint(value) @property - def transitive_marker(self): # type: () -> "BaseMarker" + def marker(self) -> BaseMarker: + return self._marker + + @marker.setter + def marker(self, marker: str | BaseMarker) -> None: + from conda_lock._vendor.poetry.core.constraints.version import parse_constraint + from conda_lock._vendor.poetry.core.packages.utils.utils import convert_markers + from conda_lock._vendor.poetry.core.version.markers import BaseMarker + from conda_lock._vendor.poetry.core.version.markers import parse_marker + + if not isinstance(marker, BaseMarker): + marker = parse_marker(marker) + + self._marker = marker + + markers = convert_markers(marker) + + if "extra" in markers: + # If we have extras, the dependency is optional + self.deactivate() + + for or_ in markers["extra"]: + for _, extra in or_: + self.in_extras.append(canonicalize_name(extra)) + + # Recalculate python versions. + self._python_versions = "*" + if not contains_group_without_marker(markers, "python_version"): + python_version_markers = markers["python_version"] + self._python_versions = normalize_python_version_markers( + python_version_markers + ) + + self._python_constraint = parse_constraint(self._python_versions) + + @property + def transitive_marker(self) -> BaseMarker: if self._transitive_marker is None: return self.marker return self._transitive_marker @transitive_marker.setter - def transitive_marker(self, value): # type: ("BaseMarker") -> None + def transitive_marker(self, value: BaseMarker) -> None: self._transitive_marker = value @property - def python_constraint(self): # type: () -> "VersionTypes" + def python_constraint(self) -> VersionConstraint: return self._python_constraint @property - def transitive_python_constraint(self): # type: () -> "VersionTypes" + def transitive_python_constraint(self) -> VersionConstraint: if self._transitive_python_constraint is None: return self._python_constraint return self._transitive_python_constraint @property - def extras(self): # type: () -> FrozenSet[str] - return self._extras + def extras(self) -> frozenset[NormalizedName]: + # extras activated in a dependency is the same as features + return self._features @property - def in_extras(self): # type: () -> list + def in_extras(self) -> list[NormalizedName]: return self._in_extras @property - def base_pep_508_name(self): # type: () -> str + def base_pep_508_name(self) -> str: + from conda_lock._vendor.poetry.core.constraints.version import Version + from conda_lock._vendor.poetry.core.constraints.version import VersionUnion + requirement = self.pretty_name if self.extras: - requirement += "[{}]".format(",".join(self.extras)) - - if isinstance(self.constraint, VersionUnion): - if self.constraint.excludes_single_version(): - requirement += " ({})".format(str(self.constraint)) + extras = ",".join(sorted(self.extras)) + requirement += f"[{extras}]" + + constraint = self.constraint + if isinstance(constraint, VersionUnion): + if ( + constraint.excludes_single_version() + or constraint.excludes_single_wildcard_range() + ): + # This branch is a short-circuit logic for special cases and + # avoids having to split and parse constraint again. This has + # no functional difference with the logic in the else branch. + requirement += f" ({str(constraint)})" else: - constraints = self.pretty_constraint.split(",") - constraints = [parse_constraint(c) for c in constraints] - constraints = [str(c) for c in constraints] - requirement += " ({})".format(",".join(constraints)) - elif isinstance(self.constraint, Version): - requirement += " (=={})".format(self.constraint.text) - elif not self.constraint.is_any(): - requirement += " ({})".format(str(self.constraint).replace(" ", "")) + constraints = ",".join( + str(parse_constraint(c)) for c in self.pretty_constraint.split(",") + ) + requirement += f" ({constraints})" + elif isinstance(constraint, Version): + requirement += f" (=={constraint.text})" + elif not constraint.is_any(): + requirement += f" ({str(constraint).replace(' ', '')})" return requirement - def allows_prereleases(self): # type: () -> bool + def allows_prereleases(self) -> bool: return self._allows_prereleases - def is_optional(self): # type: () -> bool + def is_optional(self) -> bool: return self._optional - def is_activated(self): # type: () -> bool + def is_activated(self) -> bool: return self._activated - def is_vcs(self): # type: () -> bool + def is_vcs(self) -> bool: return False - def is_file(self): # type: () -> bool + def is_file(self) -> bool: return False - def is_directory(self): # type: () -> bool + def is_directory(self) -> bool: return False - def is_url(self): # type: () -> bool + def is_url(self) -> bool: return False - def accepts(self, package): # type: (Package) -> bool - """ - Determines if the given package matches this dependency. - """ - return ( - self._name == package.name - and self._constraint.allows(package.version) - and (not package.is_prerelease() or self.allows_prereleases()) - ) + def to_pep_508(self, with_extras: bool = True) -> str: + from conda_lock._vendor.poetry.core.packages.utils.utils import convert_markers - def to_pep_508(self, with_extras=True): # type: (bool) -> str requirement = self.base_pep_508_name markers = [] @@ -244,118 +306,31 @@ def to_pep_508(self, with_extras=True): # type: (bool) -> str python_constraint = self.python_constraint markers.append( - self._create_nested_marker("python_version", python_constraint) + create_nested_marker("python_version", python_constraint) ) in_extras = " || ".join(self._in_extras) if in_extras and with_extras and not has_extras: markers.append( - self._create_nested_marker("extra", parse_generic_constraint(in_extras)) + create_nested_marker("extra", parse_generic_constraint(in_extras)) ) if markers: - if self.is_vcs() or self.is_url(): - requirement += " " - if len(markers) > 1: - markers = ["({})".format(m) for m in markers] - requirement += "; {}".format(" and ".join(markers)) + marker_str = " and ".join(f"({m})" for m in markers) else: - requirement += "; {}".format(markers[0]) + marker_str = markers[0] + requirement += f" ; {marker_str}" return requirement - def _create_nested_marker( - self, name, constraint - ): # type: (str, Union["BaseConstraint", Version, VersionConstraint]) -> str - if isinstance(constraint, (MultiConstraint, UnionConstraint)): - parts = [] - for c in constraint.constraints: - multi = False - if isinstance(c, (MultiConstraint, UnionConstraint)): - multi = True - - parts.append((multi, self._create_nested_marker(name, c))) - - glue = " and " - if isinstance(constraint, UnionConstraint): - parts = [ - "({})".format(part[1]) if part[0] else part[1] for part in parts - ] - glue = " or " - else: - parts = [part[1] for part in parts] - - marker = glue.join(parts) - elif isinstance(constraint, Constraint): - marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version) - elif isinstance(constraint, VersionUnion): - parts = [] - for c in constraint.ranges: - parts.append(self._create_nested_marker(name, c)) - - glue = " or " - parts = ["({})".format(part) for part in parts] - - marker = glue.join(parts) - elif isinstance(constraint, Version): - if constraint.precision >= 3 and name == "python_version": - name = "python_full_version" - - marker = '{} == "{}"'.format(name, constraint.text) - else: - if constraint.min is not None: - min_name = name - if constraint.min.precision >= 3 and name == "python_version": - min_name = "python_full_version" - - if constraint.max is None: - name = min_name - - op = ">=" - if not constraint.include_min: - op = ">" - - version = constraint.min.text - if constraint.max is not None: - max_name = name - if constraint.max.precision >= 3 and name == "python_version": - max_name = "python_full_version" - - text = '{} {} "{}"'.format(min_name, op, version) - - op = "<=" - if not constraint.include_max: - op = "<" - - version = constraint.max - - text += ' and {} {} "{}"'.format(max_name, op, version) - - return text - elif constraint.max is not None: - if constraint.max.precision >= 3 and name == "python_version": - name = "python_full_version" - - op = "<=" - if not constraint.include_max: - op = "<" - - version = constraint.max - else: - return "" - - marker = '{} {} "{}"'.format(name, op, version) - - return marker - - def activate(self): # type: () -> None + def activate(self) -> None: """ Set the dependency as mandatory. """ self._activated = True - def deactivate(self): # type: () -> None + def deactivate(self) -> None: """ Set the dependency as optional. """ @@ -364,56 +339,192 @@ def deactivate(self): # type: () -> None self._activated = False - def with_constraint( - self, constraint - ): # type: (Union[str, VersionConstraint]) -> Dependency - new = Dependency( - self.pretty_name, - constraint, - optional=self.is_optional(), - category=self.category, - allows_prereleases=self.allows_prereleases(), - extras=self._extras, - source_type=self._source_type, - source_url=self._source_url, - source_reference=self._source_reference, - ) + def with_constraint(self: T, constraint: str | VersionConstraint) -> T: + dependency = self.clone() + dependency.constraint = constraint # type: ignore[assignment] + return dependency + + @classmethod + def create_from_pep_508( + cls, name: str, relative_to: Path | None = None + ) -> Dependency: + """ + Resolve a PEP-508 requirement string to a `Dependency` instance. If a `relative_to` + path is specified, this is used as the base directory if the identified dependency is + of file or directory type. + """ + from conda_lock._vendor.poetry.core.packages.url_dependency import URLDependency + from conda_lock._vendor.poetry.core.packages.utils.link import Link + from conda_lock._vendor.poetry.core.packages.utils.utils import is_archive_file + from conda_lock._vendor.poetry.core.packages.utils.utils import is_python_project + from conda_lock._vendor.poetry.core.packages.utils.utils import is_url + from conda_lock._vendor.poetry.core.packages.utils.utils import path_to_url + from conda_lock._vendor.poetry.core.packages.utils.utils import strip_extras + from conda_lock._vendor.poetry.core.packages.utils.utils import url_to_path + from conda_lock._vendor.poetry.core.packages.vcs_dependency import VCSDependency + from conda_lock._vendor.poetry.core.utils.patterns import wheel_file_re + from conda_lock._vendor.poetry.core.vcs.git import ParsedUrl + from conda_lock._vendor.poetry.core.version.requirements import Requirement + + # Removing comments + parts = name.split(" #", 1) + name = parts[0].strip() + if len(parts) > 1: + rest = parts[1] + if " ;" in rest: + name += " ;" + rest.split(" ;", 1)[1] + + req = Requirement(name) + + name = req.name + link = None + + if is_url(name): + link = Link(name) + elif req.url: + link = Link(req.url) + else: + path_str = os.path.normpath(os.path.abspath(name)) + p, extras = strip_extras(path_str) + if os.path.isdir(p) and (os.path.sep in name or name.startswith(".")): + if not is_python_project(Path(name)): + raise ValueError( + f"Directory {name!r} is not installable. File 'setup.[py|cfg]' " + "not found." + ) + link = Link(path_to_url(p)) + elif is_archive_file(p): + link = Link(path_to_url(p)) + + # it's a local file, dir, or url + if link: + is_file_uri = link.scheme == "file" + is_relative_uri = is_file_uri and re.search(r"\.\./", link.url) + + # Handle relative file URLs + if is_file_uri and is_relative_uri: + path = Path(link.path) + if relative_to: + path = relative_to / path + link = Link(path_to_url(path)) + + # wheel file + version = None + if link.is_wheel: + m = wheel_file_re.match(link.filename) + if not m: + raise ValueError(f"Invalid wheel name: {link.filename}") + name = m.group("name") + version = m.group("ver") + + dep: Dependency | None = None + + if link.scheme.startswith("git+"): + url = ParsedUrl.parse(link.url) + dep = VCSDependency( + name, + "git", + url.url, + rev=url.rev, + directory=url.subdirectory, + extras=req.extras, + ) + elif link.scheme == "git": + dep = VCSDependency( + name, "git", link.url_without_fragment, extras=req.extras + ) + elif link.scheme in ["http", "https"]: + dep = URLDependency( + name, + link.url_without_fragment, + directory=link.subdirectory_fragment, + extras=req.extras, + ) + elif is_file_uri: + # handle RFC 8089 references + path = url_to_path(req.url) + dep = _make_file_or_dir_dep( + name=name, path=path, base=relative_to, extras=req.extras + ) + else: + with suppress(ValueError): + # this is a local path not using the file URI scheme + dep = _make_file_or_dir_dep( + name=name, + path=Path(req.url), + base=relative_to, + extras=req.extras, + ) - new.is_root = self.is_root - new.python_versions = self.python_versions - new.transitive_python_versions = self.transitive_python_versions - new.marker = self.marker - new.transitive_marker = self.transitive_marker + if dep is None: + dep = Dependency(name, version or "*", extras=req.extras) - for in_extra in self.in_extras: - new.in_extras.append(in_extra) + if version: + dep._constraint = parse_constraint(version) + else: + constraint: VersionConstraint | str + if req.pretty_constraint: + constraint = req.constraint + else: + constraint = "*" + dep = Dependency(name, constraint, extras=req.extras) + + if req.marker: + dep.marker = req.marker - return new + return dep - def __eq__(self, other): # type: (Any) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, Dependency): return NotImplemented - return ( - self.is_same_package_as(other) - and self._constraint == other.constraint - and self._extras == other.extras + # "constraint" is implicitly given for direct origin dependencies and might not + # be set yet ("*"). Thus, it shouldn't be used to determine if two direct origin + # dependencies are equal. + # Calling is_direct_origin() for one dependency is sufficient because + # super().__eq__() returns False for different origins. + return super().__eq__(other) and ( + self._constraint == other.constraint or self.is_direct_origin() ) - def __ne__(self, other): # type: (Any) -> bool - return not self == other - - def __hash__(self): # type: () -> int - return ( - super(Dependency, self).__hash__() - ^ hash(self._constraint) - ^ hash(self._extras) - ) + def __hash__(self) -> int: + # don't include _constraint in hash because it is mutable! + return super().__hash__() - def __str__(self): # type: () -> str + def __str__(self) -> str: if self.is_root: return self._pretty_name + if self.is_direct_origin(): + # adding version since this information is especially useful in debug output + parts = [p.strip() for p in self.base_pep_508_name.split("@", 1)] + return f"{parts[0]} ({self._pretty_constraint}) @ {parts[1]}" return self.base_pep_508_name - def __repr__(self): # type: () -> str - return "<{} {}>".format(self.__class__.__name__, str(self)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {str(self)}>" + + +def _make_file_or_dir_dep( + name: str, + path: Path, + base: Path | None = None, + extras: list[str] | None = None, +) -> FileDependency | DirectoryDependency | None: + """ + Helper function to create a file or directoru dependency with the given arguments. If + path is not a file or directory that exists, `None` is returned. + """ + from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency + from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency + + _path = path + if not path.is_absolute() and base: + # a base path was specified, so we should respect that + _path = Path(base) / path + + if _path.is_file(): + return FileDependency(name, path, base=base, extras=extras) + elif _path.is_dir(): + return DirectoryDependency(name, path, base=base, extras=extras) + + return None diff --git a/conda_lock/_vendor/poetry/core/packages/dependency_group.py b/conda_lock/_vendor/poetry/core/packages/dependency_group.py new file mode 100644 index 000000000..2bdc834d3 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/packages/dependency_group.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + + +MAIN_GROUP = "main" + + +class DependencyGroup: + def __init__(self, name: str, optional: bool = False) -> None: + self._name: str = name + self._optional: bool = optional + self._dependencies: list[Dependency] = [] + + @property + def name(self) -> str: + return self._name + + @property + def dependencies(self) -> list[Dependency]: + return self._dependencies + + def is_optional(self) -> bool: + return self._optional + + def add_dependency(self, dependency: Dependency) -> None: + self._dependencies.append(dependency) + + def remove_dependency(self, name: str) -> None: + from packaging.utils import canonicalize_name + + name = canonicalize_name(name) + + dependencies = [] + for dependency in self.dependencies: + if dependency.name == name: + continue + + dependencies.append(dependency) + + self._dependencies = dependencies + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DependencyGroup): + return NotImplemented + + return self._name == other.name and set(self._dependencies) == set( + other.dependencies + ) + + def __repr__(self) -> str: + cls = self.__class__.__name__ + return f"{cls}({self._name}, optional={self._optional})" diff --git a/conda_lock/_vendor/poetry/core/packages/directory_dependency.py b/conda_lock/_vendor/poetry/core/packages/directory_dependency.py index 88979c60b..be8de8b7b 100644 --- a/conda_lock/_vendor/poetry/core/packages/directory_dependency.py +++ b/conda_lock/_vendor/poetry/core/packages/directory_dependency.py @@ -1,29 +1,27 @@ -from typing import TYPE_CHECKING -from typing import FrozenSet -from typing import List -from typing import Union +from __future__ import annotations -from conda_lock._vendor.poetry.core.pyproject import PyProjectTOML -from conda_lock._vendor.poetry.core.utils._compat import Path +import functools +from pathlib import Path +from typing import Iterable -if TYPE_CHECKING: - from .constraints import BaseConstraint # noqa - -from .dependency import Dependency +from conda_lock._vendor.poetry.core.packages.dependency import Dependency +from conda_lock._vendor.poetry.core.packages.utils.utils import is_python_project +from conda_lock._vendor.poetry.core.packages.utils.utils import path_to_url +from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML class DirectoryDependency(Dependency): def __init__( self, - name, # type: str - path, # type: Path - category="main", # type: str - optional=False, # type: bool - base=None, # type: Path - develop=False, # type: bool - extras=None, # type: Union[List[str], FrozenSet[str]] - ): + name: str, + path: Path, + groups: Iterable[str] | None = None, + optional: bool = False, + base: Path | None = None, + develop: bool = False, + extras: Iterable[str] | None = None, + ) -> None: self._path = path self._base = base or Path.cwd() self._full_path = path @@ -32,34 +30,25 @@ def __init__( try: self._full_path = self._base.joinpath(self._path).resolve() except FileNotFoundError: - raise ValueError("Directory {} does not exist".format(self._path)) + raise ValueError(f"Directory {self._path} does not exist") self._develop = develop - self._supports_poetry = False if not self._full_path.exists(): - raise ValueError("Directory {} does not exist".format(self._path)) + raise ValueError(f"Directory {self._path} does not exist") if self._full_path.is_file(): - raise ValueError("{} is a file, expected a directory".format(self._path)) - - # Checking content to determine actions - setup = self._full_path / "setup.py" - self._supports_poetry = PyProjectTOML( - self._full_path / "pyproject.toml" - ).is_poetry_project() + raise ValueError(f"{self._path} is a file, expected a directory") - if not setup.exists() and not self._supports_poetry: + if not is_python_project(self._full_path): raise ValueError( - "Directory {} does not seem to be a Python package".format( - self._full_path - ) + f"Directory {self._full_path} does not seem to be a Python package" ) - super(DirectoryDependency, self).__init__( + super().__init__( name, "*", - category=category, + groups=groups, optional=optional, allows_prereleases=True, source_type="directory", @@ -67,72 +56,40 @@ def __init__( extras=extras, ) + # cache this function to avoid multiple IO reads and parsing + self.supports_poetry = functools.lru_cache(maxsize=1)(self._supports_poetry) + @property - def path(self): # type: () -> Path + def path(self) -> Path: return self._path @property - def full_path(self): # type: () -> Path + def full_path(self) -> Path: return self._full_path @property - def base(self): # type: () -> Path + def base(self) -> Path: return self._base @property - def develop(self): # type: () -> bool + def develop(self) -> bool: return self._develop - def supports_poetry(self): # type: () -> bool - return self._supports_poetry + def _supports_poetry(self) -> bool: + return PyProjectTOML(self._full_path / "pyproject.toml").is_poetry_project() - def is_directory(self): # type: () -> bool + def is_directory(self) -> bool: return True - def with_constraint( - self, constraint - ): # type: ("BaseConstraint") -> DirectoryDependency - new = DirectoryDependency( - self.pretty_name, - path=self.path, - base=self.base, - optional=self.is_optional(), - category=self.category, - develop=self._develop, - extras=self._extras, - ) - - new._constraint = constraint - new._pretty_constraint = str(constraint) - - new.is_root = self.is_root - new.python_versions = self.python_versions - new.marker = self.marker - new.transitive_marker = self.transitive_marker - - for in_extra in self.in_extras: - new.in_extras.append(in_extra) - - return new - @property - def base_pep_508_name(self): # type: () -> str + def base_pep_508_name(self) -> str: requirement = self.pretty_name if self.extras: - requirement += "[{}]".format(",".join(self.extras)) + extras = ",".join(sorted(self.extras)) + requirement += f"[{extras}]" - requirement += " @ {}".format(self._path.as_posix()) + path = path_to_url(self.full_path) + requirement += f" @ {path}" return requirement - - def __str__(self): # type: () -> str - if self.is_root: - return self._pretty_name - - return "{} ({} {})".format( - self._pretty_name, self._pretty_constraint, self._path.as_posix() - ) - - def __hash__(self): # type: () -> int - return hash((self._name, self._full_path.as_posix())) diff --git a/conda_lock/_vendor/poetry/core/packages/file_dependency.py b/conda_lock/_vendor/poetry/core/packages/file_dependency.py index f3b9593e8..d83f699f6 100644 --- a/conda_lock/_vendor/poetry/core/packages/file_dependency.py +++ b/conda_lock/_vendor/poetry/core/packages/file_dependency.py @@ -1,31 +1,25 @@ +from __future__ import annotations + import hashlib import io -from typing import TYPE_CHECKING -from typing import FrozenSet -from typing import List -from typing import Union +from pathlib import Path +from typing import Iterable +from conda_lock._vendor.poetry.core.packages.dependency import Dependency from conda_lock._vendor.poetry.core.packages.utils.utils import path_to_url -from conda_lock._vendor.poetry.core.utils._compat import Path - -from .dependency import Dependency - - -if TYPE_CHECKING: - from .constraints import BaseConstraint class FileDependency(Dependency): def __init__( self, - name, # type: str - path, # type: Path - category="main", # type: str - optional=False, # type: bool - base=None, # type: Path - extras=None, # type: Union[List[str], FrozenSet[str]] - ): + name: str, + path: Path, + groups: Iterable[str] | None = None, + optional: bool = False, + base: Path | None = None, + extras: Iterable[str] | None = None, + ) -> None: self._path = path self._base = base or Path.cwd() self._full_path = path @@ -34,18 +28,18 @@ def __init__( try: self._full_path = self._base.joinpath(self._path).resolve() except FileNotFoundError: - raise ValueError("Directory {} does not exist".format(self._path)) + raise ValueError(f"Directory {self._path} does not exist") if not self._full_path.exists(): - raise ValueError("File {} does not exist".format(self._path)) + raise ValueError(f"File {self._path} does not exist") if self._full_path.is_dir(): - raise ValueError("{} is a directory, expected a file".format(self._path)) + raise ValueError(f"{self._path} is a directory, expected a file") - super(FileDependency, self).__init__( + super().__init__( name, "*", - category=category, + groups=groups, optional=optional, allows_prereleases=True, source_type="file", @@ -54,21 +48,21 @@ def __init__( ) @property - def base(self): # type: () -> Path + def base(self) -> Path: return self._base @property - def path(self): # type: () -> Path + def path(self) -> Path: return self._path @property - def full_path(self): # type: () -> Path + def full_path(self) -> Path: return self._full_path - def is_file(self): # type: () -> bool + def is_file(self) -> bool: return True - def hash(self, hash_name="sha256"): # type: (str) -> str + def hash(self, hash_name: str = "sha256") -> str: h = hashlib.new(hash_name) with self._full_path.open("rb") as fp: for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""): @@ -76,48 +70,15 @@ def hash(self, hash_name="sha256"): # type: (str) -> str return h.hexdigest() - def with_constraint(self, constraint): # type: ("BaseConstraint") -> FileDependency - new = FileDependency( - self.pretty_name, - path=self.path, - base=self.base, - optional=self.is_optional(), - category=self.category, - extras=self._extras, - ) - - new._constraint = constraint - new._pretty_constraint = str(constraint) - - new.is_root = self.is_root - new.python_versions = self.python_versions - new.marker = self.marker - new.transitive_marker = self.transitive_marker - - for in_extra in self.in_extras: - new.in_extras.append(in_extra) - - return new - @property - def base_pep_508_name(self): # type: () -> str + def base_pep_508_name(self) -> str: requirement = self.pretty_name if self.extras: - requirement += "[{}]".format(",".join(self.extras)) + extras = ",".join(sorted(self.extras)) + requirement += f"[{extras}]" - path = path_to_url(self.path) if self.path.is_absolute() else self.path - requirement += " @ {}".format(path) + path = path_to_url(self.full_path) + requirement += f" @ {path}" return requirement - - def __str__(self): # type: () -> str - if self.is_root: - return self._pretty_name - - return "{} ({} {})".format( - self._pretty_name, self._pretty_constraint, self._path - ) - - def __hash__(self): # type: () -> int - return hash((self._name, self._full_path)) diff --git a/conda_lock/_vendor/poetry/core/packages/package.py b/conda_lock/_vendor/poetry/core/packages/package.py index 4b172c0c2..d2586c803 100644 --- a/conda_lock/_vendor/poetry/core/packages/package.py +++ b/conda_lock/_vendor/poetry/core/packages/package.py @@ -1,42 +1,41 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import copy import re from contextlib import contextmanager +from pathlib import Path from typing import TYPE_CHECKING -from typing import Dict -from typing import List -from typing import Optional -from typing import Union - -from conda_lock._vendor.poetry.core.semver import Version -from conda_lock._vendor.poetry.core.semver import parse_constraint -from conda_lock._vendor.poetry.core.spdx import License -from conda_lock._vendor.poetry.core.spdx import license_by_id -from conda_lock._vendor.poetry.core.version.markers import AnyMarker +from typing import Collection +from typing import Iterable +from typing import Iterator +from typing import TypeVar + +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint +from conda_lock._vendor.poetry.core.constraints.version.exceptions import ParseConstraintError +from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP +from conda_lock._vendor.poetry.core.packages.specification import PackageSpecification +from conda_lock._vendor.poetry.core.packages.utils.utils import create_nested_marker +from conda_lock._vendor.poetry.core.version.exceptions import InvalidVersion from conda_lock._vendor.poetry.core.version.markers import parse_marker -# Do not move to the TYPE_CHECKING only section, because Dependency get's imported -# by poetry/packages/locker.py from here -from .dependency import Dependency -from .specification import PackageSpecification -from .utils.utils import create_nested_marker - if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.semver import VersionTypes # noqa - from conda_lock._vendor.poetry.core.version.markers import BaseMarker # noqa + from packaging.utils import NormalizedName - from .directory_dependency import DirectoryDependency - from .file_dependency import FileDependency - from .url_dependency import URLDependency - from .vcs_dependency import VCSDependency + from conda_lock._vendor.poetry.core.constraints.version import Version + from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.dependency_group import DependencyGroup + from conda_lock._vendor.poetry.core.spdx.license import License + from conda_lock._vendor.poetry.core.version.markers import BaseMarker -AUTHOR_REGEX = re.compile(r"(?u)^(?P[- .,\w\d'’\"()&]+)(?: <(?P.+?)>)?$") + T = TypeVar("T", bound="Package") +AUTHOR_REGEX = re.compile(r"(?u)^(?P[- .,\w\d'’\"():&]+)(?: <(?P.+?)>)?$") -class Package(PackageSpecification): +class Package(PackageSpecification): AVAILABLE_PYTHONS = { "2", "2.7", @@ -48,153 +47,187 @@ class Package(PackageSpecification): "3.8", "3.9", "3.10", + "3.11", } def __init__( self, - name, # type: str - version, # type: Union[str, Version] - pretty_version=None, # type: Optional[str] - source_type=None, # type: Optional[str] - source_url=None, # type: Optional[str] - source_reference=None, # type: Optional[str] - source_resolved_reference=None, # type: Optional[str] - features=None, # type: Optional[List[str]] - ): + name: str, + version: str | Version, + pretty_version: str | None = None, + source_type: str | None = None, + source_url: str | None = None, + source_reference: str | None = None, + source_resolved_reference: str | None = None, + source_subdirectory: str | None = None, + features: Iterable[str] | None = None, + develop: bool = False, + yanked: str | bool = False, + ) -> None: """ Creates a new in memory package. """ - super(Package, self).__init__( + from conda_lock._vendor.poetry.core.version.markers import AnyMarker + + super().__init__( name, source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, + source_subdirectory=source_subdirectory, features=features, ) - if not isinstance(version, Version): - self._version = Version.parse(version) - self._pretty_version = pretty_version or version - else: - self._version = version - self._pretty_version = pretty_version or self._version.text + self._set_version(version, pretty_version) self.description = "" - self._authors = [] - self._maintainers = [] + self._authors: list[str] = [] + self._maintainers: list[str] = [] + + self.homepage: str | None = None + self.repository_url: str | None = None + self.documentation_url: str | None = None + self.keywords: list[str] = [] + self._license: License | None = None + self.readmes: tuple[Path, ...] = () - self.homepage = None - self.repository_url = None - self.documentation_url = None - self.keywords = [] - self._license = None - self.readme = None + self.extras: dict[NormalizedName, list[Dependency]] = {} - self.requires = [] - self.dev_requires = [] - self.extras = {} - self.requires_extras = [] + self._dependency_groups: dict[str, DependencyGroup] = {} + # For compatibility with previous version, we keep the category self.category = "main" - self.files = [] + self.files: list[dict[str, str]] = [] self.optional = False - self.classifiers = [] + self.classifiers: list[str] = [] self._python_versions = "*" self._python_constraint = parse_constraint("*") - self._python_marker = AnyMarker() + self._python_marker: BaseMarker = AnyMarker() self.platform = None - self.marker = AnyMarker() + self.marker: BaseMarker = AnyMarker() + + self.root_dir: Path | None = None - self.root_dir = None + self.develop = develop - self.develop = True + self._yanked = yanked @property - def name(self): # type: () -> str + def name(self) -> NormalizedName: return self._name @property - def pretty_name(self): # type: () -> str + def pretty_name(self) -> str: return self._pretty_name @property - def version(self): # type: () -> "Version" + def version(self) -> Version: return self._version @property - def pretty_version(self): # type: () -> str + def pretty_version(self) -> str: return self._pretty_version @property - def unique_name(self): # type: () -> str + def unique_name(self) -> str: if self.is_root(): return self._name return self.complete_name + "-" + self._version.text @property - def pretty_string(self): # type: () -> str + def pretty_string(self) -> str: return self.pretty_name + " " + self.pretty_version @property - def full_pretty_version(self): # type: () -> str + def full_pretty_version(self) -> str: if self.source_type in ["file", "directory", "url"]: - return "{} {}".format(self._pretty_version, self.source_url) + return f"{self._pretty_version} {self.source_url}" if self.source_type not in ["hg", "git"]: return self._pretty_version - if self.source_resolved_reference: - if len(self.source_resolved_reference) == 40: - return "{} {}".format( - self._pretty_version, self.source_resolved_reference[0:7] - ) + ref: str | None + if self.source_resolved_reference and len(self.source_resolved_reference) == 40: + ref = self.source_resolved_reference[0:7] + return f"{self._pretty_version} {ref}" # if source reference is a sha1 hash -- truncate - if len(self.source_reference) == 40: - return "{} {}".format(self._pretty_version, self.source_reference[0:7]) + if self.source_reference and len(self.source_reference) == 40: + return f"{self._pretty_version} {self.source_reference[0:7]}" - return "{} {}".format( - self._pretty_version, - self._source_resolved_reference or self._source_reference, - ) + ref = self._source_resolved_reference or self._source_reference + return f"{self._pretty_version} {ref}" @property - def authors(self): # type: () -> list + def authors(self) -> list[str]: return self._authors @property - def author_name(self): # type: () -> str + def author_name(self) -> str | None: return self._get_author()["name"] @property - def author_email(self): # type: () -> str + def author_email(self) -> str | None: return self._get_author()["email"] @property - def maintainers(self): # type: () -> list + def maintainers(self) -> list[str]: return self._maintainers @property - def maintainer_name(self): # type: () -> str + def maintainer_name(self) -> str | None: return self._get_maintainer()["name"] @property - def maintainer_email(self): # type: () -> str + def maintainer_email(self) -> str | None: return self._get_maintainer()["email"] + @property + def requires(self) -> list[Dependency]: + """ + Returns the main dependencies + """ + if not self._dependency_groups or MAIN_GROUP not in self._dependency_groups: + return [] + + return self._dependency_groups[MAIN_GROUP].dependencies + @property def all_requires( self, - ): # type: () -> List[Union["DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency", Dependency]] - return self.requires + self.dev_requires + ) -> list[Dependency]: + """ + Returns the main dependencies and group dependencies. + """ + return [ + dependency + for group in self._dependency_groups.values() + for dependency in group.dependencies + ] + + def _set_version( + self, version: str | Version, pretty_version: str | None = None + ) -> None: + from conda_lock._vendor.poetry.core.constraints.version import Version + + if not isinstance(version, Version): + try: + version = Version.parse(version) + except InvalidVersion: + raise InvalidVersion( + f"Invalid version '{version}' on package {self.name}" + ) + + self._version = version + self._pretty_version = pretty_version or version.text - def _get_author(self): # type: () -> dict + def _get_author(self) -> dict[str, str | None]: if not self._authors: return {"name": None, "email": None} @@ -211,7 +244,7 @@ def _get_author(self): # type: () -> dict return {"name": name, "email": email} - def _get_maintainer(self): # type: () -> dict + def _get_maintainer(self) -> dict[str, str | None]: if not self._maintainers: return {"name": None, "email": None} @@ -229,40 +262,48 @@ def _get_maintainer(self): # type: () -> dict return {"name": name, "email": email} @property - def python_versions(self): # type: () -> str + def python_versions(self) -> str: return self._python_versions @python_versions.setter - def python_versions(self, value): # type: (str) -> None + def python_versions(self, value: str) -> None: + try: + constraint = parse_constraint(value) + except ParseConstraintError: + raise ParseConstraintError(f"Invalid python versions '{value}' on {self}") + self._python_versions = value - self._python_constraint = parse_constraint(value) + self._python_constraint = constraint self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint) ) @property - def python_constraint(self): # type: () -> "VersionTypes" + def python_constraint(self) -> VersionConstraint: return self._python_constraint @property - def python_marker(self): # type: () -> "BaseMarker" + def python_marker(self) -> BaseMarker: return self._python_marker @property - def license(self): # type: () -> License + def license(self) -> License | None: return self._license @license.setter - def license(self, value): # type: (Optional[str, License]) -> None - if value is None: - self._license = value - elif isinstance(value, License): + def license(self, value: str | License | None) -> None: + from conda_lock._vendor.poetry.core.spdx.helpers import license_by_id + from conda_lock._vendor.poetry.core.spdx.license import License + + if value is None or isinstance(value, License): self._license = value else: self._license = license_by_id(value) @property - def all_classifiers(self): # type: () -> List[str] + def all_classifiers(self) -> list[str]: + from conda_lock._vendor.poetry.core.constraints.version import Version + classifiers = copy.copy(self.classifiers) # Automatically set python classifiers @@ -271,27 +312,48 @@ def all_classifiers(self): # type: () -> List[str] else: python_constraint = self.python_constraint - for version in sorted(self.AVAILABLE_PYTHONS): + python_classifier_prefix = "Programming Language :: Python" + python_classifiers = [] + + # we sort python versions by sorting an int tuple of (major, minor) version + # to ensure we sort 3.10 after 3.9 + for version in sorted( + self.AVAILABLE_PYTHONS, key=lambda x: tuple(map(int, x.split("."))) + ): if len(version) == 1: constraint = parse_constraint(version + ".*") else: constraint = Version.parse(version) if python_constraint.allows_any(constraint): - classifiers.append( - "Programming Language :: Python :: {}".format(version) - ) + classifier = f"{python_classifier_prefix} :: {version}" + if classifier not in python_classifiers: + python_classifiers.append(classifier) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) - classifiers = set(classifiers) + # Sort classifiers and insert python classifiers at the right location. We do + # it like this so that 3.10 is sorted after 3.9. + sorted_classifiers = [] + python_classifiers_inserted = False + for classifier in sorted(set(classifiers)): + if ( + not python_classifiers_inserted + and classifier > python_classifier_prefix + ): + sorted_classifiers.extend(python_classifiers) + python_classifiers_inserted = True + sorted_classifiers.append(classifier) + + if not python_classifiers_inserted: + sorted_classifiers.extend(python_classifiers) - return sorted(classifiers) + return sorted_classifiers @property - def urls(self): # type: () -> Dict[str, str] + def urls(self) -> dict[str, str]: urls = {} if self.homepage: @@ -305,68 +367,172 @@ def urls(self): # type: () -> Dict[str, str] return urls - def is_prerelease(self): # type: () -> bool - return self._version.is_prerelease() + @property + def readme(self) -> Path | None: + import warnings - def is_root(self): # type: () -> bool + warnings.warn( + "`readme` is deprecated: you are getting only the first readme file. Please" + " use the plural form `readmes`.", + DeprecationWarning, + ) + return next(iter(self.readmes), None) + + @readme.setter + def readme(self, path: Path) -> None: + import warnings + + warnings.warn( + "`readme` is deprecated. Please assign a tuple to the plural form" + " `readmes`.", + DeprecationWarning, + ) + self.readmes = (path,) + + @property + def yanked(self) -> bool: + return isinstance(self._yanked, str) or bool(self._yanked) + + @property + def yanked_reason(self) -> str: + if isinstance(self._yanked, str): + return self._yanked + return "" + + def is_prerelease(self) -> bool: + return self._version.is_unstable() + + def is_root(self) -> bool: return False + def dependency_group_names(self, include_optional: bool = False) -> set[str]: + return { + name + for name, group in self._dependency_groups.items() + if not group.is_optional() or include_optional + } + + def add_dependency_group(self, group: DependencyGroup) -> None: + self._dependency_groups[group.name] = group + + def has_dependency_group(self, name: str) -> bool: + return name in self._dependency_groups + + def dependency_group(self, name: str) -> DependencyGroup: + if not self.has_dependency_group(name): + raise ValueError(f'The dependency group "{name}" does not exist.') + + return self._dependency_groups[name] + def add_dependency( - self, dependency, - ): # type: (Dependency) -> Dependency - if dependency.category == "dev": - self.dev_requires.append(dependency) - else: - self.requires.append(dependency) + self, + dependency: Dependency, + ) -> Dependency: + from conda_lock._vendor.poetry.core.packages.dependency_group import DependencyGroup + + for group_name in dependency.groups: + if group_name not in self._dependency_groups: + # Dynamically add the dependency group + self.add_dependency_group(DependencyGroup(group_name)) + + self._dependency_groups[group_name].add_dependency(dependency) return dependency - def to_dependency( - self, - ): # type: () -> Union[Dependency, "DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency"] - from conda_lock._vendor.poetry.core.utils._compat import Path + def without_dependency_groups(self: T, groups: Collection[str]) -> T: + """ + Returns a clone of the package with the given dependency groups excluded. + """ + package = self.clone() + + for group_name in groups: + if group_name in package._dependency_groups: + del package._dependency_groups[group_name] + + return package + + def without_optional_dependency_groups(self: T) -> T: + """ + Returns a clone of the package without optional dependency groups. + """ + package = self.clone() + + for group_name, group in self._dependency_groups.items(): + if group.is_optional(): + del package._dependency_groups[group_name] + + return package + + def with_dependency_groups( + self: T, groups: Collection[str], only: bool = False + ) -> T: + """ + Returns a clone of the package with the given dependency groups opted in. + + Note that it will return all dependencies across all groups + more the given, optional, groups. + + If `only` is set to True, then only the given groups will be selected. + """ + package = self.clone() + + for group_name, group in self._dependency_groups.items(): + if (only or group.is_optional()) and group_name not in groups: + del package._dependency_groups[group_name] + + return package + + def to_dependency(self) -> Dependency: + from pathlib import Path - from .dependency import Dependency - from .directory_dependency import DirectoryDependency - from .file_dependency import FileDependency - from .url_dependency import URLDependency - from .vcs_dependency import VCSDependency + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency + from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency + from conda_lock._vendor.poetry.core.packages.url_dependency import URLDependency + from conda_lock._vendor.poetry.core.packages.vcs_dependency import VCSDependency + dep: Dependency if self.source_type == "directory": + assert self._source_url is not None dep = DirectoryDependency( self._name, Path(self._source_url), - category=self.category, + groups=list(self._dependency_groups.keys()), optional=self.optional, base=self.root_dir, develop=self.develop, extras=self.features, ) elif self.source_type == "file": + assert self._source_url is not None dep = FileDependency( self._name, Path(self._source_url), - category=self.category, + groups=list(self._dependency_groups.keys()), optional=self.optional, base=self.root_dir, extras=self.features, ) elif self.source_type == "url": + assert self._source_url is not None dep = URLDependency( self._name, self._source_url, - category=self.category, + directory=self.source_subdirectory, + groups=list(self._dependency_groups.keys()), optional=self.optional, extras=self.features, ) elif self.source_type == "git": + assert self._source_url is not None dep = VCSDependency( self._name, self.source_type, - self.source_url, + self._source_url, rev=self.source_reference, resolved_rev=self.source_resolved_reference, - category=self.category, + directory=self.source_subdirectory, + groups=list(self._dependency_groups.keys()), optional=self.optional, develop=self.develop, extras=self.features, @@ -380,13 +546,13 @@ def to_dependency( if not self.python_constraint.is_any(): dep.python_versions = self.python_versions - if self._source_type not in ["directory", "file", "url", "git"]: + if not self.is_direct_origin(): return dep return dep.with_constraint(self._version) @contextmanager - def with_python_versions(self, python_versions): # type: (str) -> None + def with_python_versions(self, python_versions: str) -> Iterator[None]: original_python_versions = self.python_versions self.python_versions = python_versions @@ -395,51 +561,85 @@ def with_python_versions(self, python_versions): # type: (str) -> None self.python_versions = original_python_versions - def with_features(self, features): # type: (List[str]) -> "Package" - package = self.clone() - - package._features = frozenset(features) - - return package - - def without_features(self): # type: () -> "Package" - return self.with_features([]) + def satisfies( + self, dependency: Dependency, ignore_source_type: bool = False + ) -> bool: + """ + Helper method to check if this package satisfies a given dependency. - def clone(self): # type: () -> "Package" - clone = self.__class__(self.pretty_name, self.version) - clone.__dict__ = copy.deepcopy(self.__dict__) - return clone + This is determined by assessing if this instance provides the package specified + by the given dependency. Further, version and source types are checked. + """ + if self.name != dependency.name: + return False + + if not dependency.constraint.allows(self.version): + return False + + if not ignore_source_type and not self.source_satisfies(dependency): + return False + + return True + + def source_satisfies(self, dependency: Dependency) -> bool: + """Determine whether this package's source satisfies the given dependency.""" + if dependency.source_type is None: + if dependency.source_name is None: + # The dependency doesn't care about the source, so this package + # certainly satisfies it. + return True + + # The dependency specifies a source_name but not a type: it wants either + # pypi or a legacy repository. + # + # - If this package has no source type then it's from pypi, so it + # matches if and only if that's what the dependency wants + # - Else this package is a match if and only if it is from the desired + # repository + if self.source_type is None: + return dependency.source_name.lower() == "pypi" + + return ( + self.source_type == "legacy" + and self.source_reference is not None + and self.source_reference.lower() == dependency.source_name.lower() + ) - def __hash__(self): # type: () -> int - return super(Package, self).__hash__() ^ hash(self._version) + # The dependency specifies a source: this package matches if and only if it is + # from that source. + return dependency.is_same_source_as(self) - def __eq__(self, other): # type: (Package) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, Package): return NotImplemented - return self.is_same_package_as(other) and self._version == other.version + return super().__eq__(other) and self._version == other.version + + def __hash__(self) -> int: + return super().__hash__() ^ hash(self._version) - def __str__(self): # type: () -> str - return "{} ({})".format(self.complete_name, self.full_pretty_version) + def __str__(self) -> str: + return f"{self.complete_name} ({self.full_pretty_version})" - def __repr__(self): # type: () -> str + def __repr__(self) -> str: args = [repr(self._name), repr(self._version.text)] if self._features: - args.append("features={}".format(repr(self._features))) + args.append(f"features={repr(self._features)}") if self._source_type: - args.append("source_type={}".format(repr(self._source_type))) - args.append("source_url={}".format(repr(self._source_url))) + args.append(f"source_type={repr(self._source_type)}") + args.append(f"source_url={repr(self._source_url)}") if self._source_reference: - args.append("source_reference={}".format(repr(self._source_reference))) + args.append(f"source_reference={repr(self._source_reference)}") if self._source_resolved_reference: args.append( - "source_resolved_reference={}".format( - repr(self._source_resolved_reference) - ) + f"source_resolved_reference={repr(self._source_resolved_reference)}" ) + if self._source_subdirectory: + args.append(f"source_subdirectory={repr(self._source_subdirectory)}") - return "Package({})".format(", ".join(args)) + args_str = ", ".join(args) + return f"Package({args_str})" diff --git a/conda_lock/_vendor/poetry/core/packages/project_package.py b/conda_lock/_vendor/poetry/core/packages/project_package.py index 5c3c70598..eb84548f7 100644 --- a/conda_lock/_vendor/poetry/core/packages/project_package.py +++ b/conda_lock/_vendor/poetry/core/packages/project_package.py @@ -1,67 +1,61 @@ +from __future__ import annotations + from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import Optional -from typing import Union -from conda_lock._vendor.poetry.core.semver import VersionRange -from conda_lock._vendor.poetry.core.semver import parse_constraint +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint from conda_lock._vendor.poetry.core.version.markers import parse_marker if TYPE_CHECKING: - from . import ( - DirectoryDependency, - FileDependency, - URLDependency, - VCSDependency, - Dependency, - ) + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.constraints.version import Version -from .package import Package -from .utils.utils import create_nested_marker +from conda_lock._vendor.poetry.core.packages.package import Package +from conda_lock._vendor.poetry.core.packages.utils.utils import create_nested_marker class ProjectPackage(Package): def __init__( - self, name, version, pretty_version=None - ): # type: (str, Union[str, VersionRange], Optional[str]) -> None - super(ProjectPackage, self).__init__(name, version, pretty_version) - - self.build_config = dict() - self.packages = [] - self.include = [] - self.exclude = [] - self.custom_urls = {} + self, + name: str, + version: str | Version, + pretty_version: str | None = None, + ) -> None: + super().__init__(name, version, pretty_version) + + self.build_config: dict[str, Any] = {} + self.packages: list[dict[str, Any]] = [] + self.include: list[dict[str, Any]] = [] + self.exclude: list[dict[str, Any]] = [] + self.custom_urls: dict[str, str] = {} if self._python_versions == "*": self._python_constraint = parse_constraint("~2.7 || >=3.4") @property - def build_script(self): # type: () -> Optional[str] + def build_script(self) -> str | None: return self.build_config.get("script") - def is_root(self): # type: () -> bool + def is_root(self) -> bool: return True - def to_dependency( - self, - ): # type: () -> Union["DirectoryDependency", "FileDependency", "URLDependency", "VCSDependency", "Dependency"] - dependency = super(ProjectPackage, self).to_dependency() + def to_dependency(self) -> Dependency: + dependency = super().to_dependency() dependency.is_root = True return dependency @property - def python_versions(self): # type: () -> Union[str, VersionRange] + def python_versions(self) -> str: return self._python_versions @python_versions.setter - def python_versions(self, value): # type: (Union[str, VersionRange]) -> None + def python_versions(self, value: str) -> None: self._python_versions = value - if value == "*" or value == VersionRange(): + if value == "*": value = "~2.7 || >=3.4" self._python_constraint = parse_constraint(value) @@ -70,12 +64,27 @@ def python_versions(self, value): # type: (Union[str, VersionRange]) -> None ) @property - def urls(self): # type: () -> Dict[str, Any] - urls = super(ProjectPackage, self).urls + def version(self) -> Version: + # override version to make it settable + return super().version + + @version.setter + def version(self, value: str | Version) -> None: + self._set_version(value) + + @property + def urls(self) -> dict[str, str]: + urls = super().urls urls.update(self.custom_urls) return urls - def build_should_generate_setup(self): # type: () -> bool + def __hash__(self) -> int: + # The parent Package class's __hash__ incorporates the version because + # a Package's version is immutable. But a ProjectPackage's version is + # mutable. So call Package's parent hash function. + return super(Package, self).__hash__() + + def build_should_generate_setup(self) -> bool: return self.build_config.get("generate-setup-file", True) diff --git a/conda_lock/_vendor/poetry/core/packages/specification.py b/conda_lock/_vendor/poetry/core/packages/specification.py index 3ab4937f5..b2dd9b076 100644 --- a/conda_lock/_vendor/poetry/core/packages/specification.py +++ b/conda_lock/_vendor/poetry/core/packages/specification.py @@ -1,118 +1,202 @@ -from typing import FrozenSet -from typing import List -from typing import Optional +from __future__ import annotations -from conda_lock._vendor.poetry.core.utils.helpers import canonicalize_name +import copy +from typing import TYPE_CHECKING +from typing import Iterable +from typing import TypeVar -class PackageSpecification(object): +from packaging.utils import canonicalize_name + + +if TYPE_CHECKING: + from packaging.utils import NormalizedName + + T = TypeVar("T", bound="PackageSpecification") + + +class PackageSpecification: def __init__( self, - name, # type: str - source_type=None, # type: Optional[str] - source_url=None, # type: Optional[str] - source_reference=None, # type: Optional[str] - source_resolved_reference=None, # type: Optional[str] - features=None, # type: Optional[List[str]] - ): + name: str, + source_type: str | None = None, + source_url: str | None = None, + source_reference: str | None = None, + source_resolved_reference: str | None = None, + source_subdirectory: str | None = None, + features: Iterable[str] | None = None, + ) -> None: + from packaging.utils import canonicalize_name + self._pretty_name = name self._name = canonicalize_name(name) self._source_type = source_type self._source_url = source_url self._source_reference = source_reference self._source_resolved_reference = source_resolved_reference + self._source_subdirectory = source_subdirectory if not features: features = [] - self._features = frozenset(features) + self._features = frozenset(canonicalize_name(feature) for feature in features) @property - def name(self): # type: () -> str + def name(self) -> NormalizedName: return self._name @property - def pretty_name(self): # type: () -> str + def pretty_name(self) -> str: return self._pretty_name @property - def complete_name(self): # type: () -> str - name = self._name + def complete_name(self) -> str: + name: str = self._name if self._features: - name = "{}[{}]".format(name, ",".join(sorted(self._features))) + features = ",".join(sorted(self._features)) + name = f"{name}[{features}]" return name @property - def source_type(self): # type: () -> Optional[str] + def source_type(self) -> str | None: return self._source_type @property - def source_url(self): # type: () -> Optional[str] + def source_url(self) -> str | None: return self._source_url @property - def source_reference(self): # type: () -> Optional[str] + def source_reference(self) -> str | None: return self._source_reference @property - def source_resolved_reference(self): # type: () -> Optional[str] + def source_resolved_reference(self) -> str | None: return self._source_resolved_reference @property - def features(self): # type: () -> FrozenSet[str] + def source_subdirectory(self) -> str | None: + return self._source_subdirectory + + @property + def features(self) -> frozenset[NormalizedName]: return self._features - def is_same_package_as(self, other): # type: ("PackageSpecification") -> bool - if other.complete_name != self.complete_name: + def is_direct_origin(self) -> bool: + return self._source_type in [ + "directory", + "file", + "url", + "git", + ] + + def provides(self, other: PackageSpecification) -> bool: + """ + Helper method to determine if this package provides the given specification. + + This determination is made to be true, if the names are the same and this + package provides all features required by the other specification. + + Source type checks are explicitly ignored here as this is not of interest. + """ + return self.name == other.name and self.features.issuperset(other.features) + + def is_same_source_as(self, other: PackageSpecification) -> bool: + if self._source_type != other.source_type: return False - if self._source_type: - if self._source_type != other.source_type: + if not self._source_type: + # both packages are of source type None + # no need to check further + return True + + if ( + self._source_url or other.source_url + ) and self._source_url != other.source_url: + return False + + if ( + self._source_subdirectory or other.source_subdirectory + ) and self._source_subdirectory != other.source_subdirectory: + return False + + # We check the resolved reference first: + # if they match we assume equality regardless + # of their source reference. + # This is important when comparing a resolved branch VCS + # dependency to a direct commit reference VCS dependency + if ( + self._source_resolved_reference + and other.source_resolved_reference + and self._source_resolved_reference == other.source_resolved_reference + ): + return True + + if self._source_reference or other.source_reference: + # special handling for packages with references + if not self._source_reference or not other.source_reference: + # case: one reference is defined and is non-empty, but other is not return False - if self._source_url or other.source_url: - if self._source_url != other.source_url: - return False - - if self._source_reference or other.source_reference: - # special handling for packages with references - if not self._source_reference or not other.source_reference: - # case: one reference is defined and is non-empty, but other is not - return False - - if not ( - self._source_reference == other.source_reference - or self._source_reference.startswith(other.source_reference) - or other.source_reference.startswith(self._source_reference) - ): - # case: both references defined, but one is not equal to or a short - # representation of the other - return False - - if ( - self._source_resolved_reference - and other.source_resolved_reference - and self._source_resolved_reference - != other.source_resolved_reference - ): - return False + if not ( + self._source_reference == other.source_reference + or self._source_reference.startswith(other.source_reference) + or other.source_reference.startswith(self._source_reference) + ): + # case: both references defined, but one is not equal to or a short + # representation of the other + return False + + if ( + self._source_resolved_reference + and other.source_resolved_reference + and self._source_resolved_reference != other.source_resolved_reference + ): + return False return True - def __hash__(self): # type: () -> int - if not self._source_type: - return hash(self._name) - - return ( - hash(self._name) - ^ hash(self._source_type) - ^ hash(self._source_url) - ^ hash(self._source_reference) - ^ hash(self._source_resolved_reference) - ^ hash(self._features) + def is_same_package_as(self, other: PackageSpecification) -> bool: + if other.complete_name != self.complete_name: + return False + + return self.is_same_source_as(other) + + def clone(self: T) -> T: + return copy.deepcopy(self) + + def with_features(self: T, features: Iterable[str]) -> T: + package = self.clone() + + package._features = frozenset( + canonicalize_name(feature) for feature in features ) - def __str__(self): # type: () -> str + return package + + def without_features(self: T) -> T: + return self.with_features([]) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PackageSpecification): + return NotImplemented + return self.is_same_package_as(other) + + def __hash__(self) -> int: + result = hash(self.complete_name) # complete_name includes features + + if self._source_type: + # Don't include _source_reference and _source_resolved_reference in hash + # because two specs can be equal even if these attributes are not equal. + # (They must still meet certain conditions. See is_same_source_as().) + result ^= ( + hash(self._source_type) + ^ hash(self._source_url) + ^ hash(self._source_subdirectory) + ) + + return result + + def __str__(self) -> str: raise NotImplementedError() diff --git a/conda_lock/_vendor/poetry/core/packages/url_dependency.py b/conda_lock/_vendor/poetry/core/packages/url_dependency.py index 344eb587e..0736f25a7 100644 --- a/conda_lock/_vendor/poetry/core/packages/url_dependency.py +++ b/conda_lock/_vendor/poetry/core/packages/url_dependency.py @@ -1,85 +1,63 @@ -from typing import TYPE_CHECKING -from typing import FrozenSet -from typing import List -from typing import Union +from __future__ import annotations -from conda_lock._vendor.poetry.core.utils._compat import urlparse +from typing import Iterable +from urllib.parse import urlparse -from .dependency import Dependency - - -if TYPE_CHECKING: - from .constraints import BaseConstraint +from conda_lock._vendor.poetry.core.packages.dependency import Dependency class URLDependency(Dependency): def __init__( self, - name, # type: str - url, # type: str - category="main", # type: str - optional=False, # type: bool - extras=None, # type: Union[List[str], FrozenSet[str]] - ): + name: str, + url: str, + *, + directory: str | None = None, + groups: Iterable[str] | None = None, + optional: bool = False, + extras: Iterable[str] | None = None, + ) -> None: self._url = url + self._directory = directory - parsed = urlparse.urlparse(url) + parsed = urlparse(url) if not parsed.scheme or not parsed.netloc: - raise ValueError("{} does not seem like a valid url".format(url)) + raise ValueError(f"{url} does not seem like a valid url") - super(URLDependency, self).__init__( + super().__init__( name, "*", - category=category, + groups=groups, optional=optional, allows_prereleases=True, source_type="url", source_url=self._url, + source_subdirectory=directory, extras=extras, ) @property - def url(self): # type: () -> str + def url(self) -> str: return self._url @property - def base_pep_508_name(self): # type: () -> str + def directory(self) -> str | None: + return self._directory + + @property + def base_pep_508_name(self) -> str: requirement = self.pretty_name if self.extras: - requirement += "[{}]".format(",".join(self.extras)) + extras = ",".join(sorted(self.extras)) + requirement += f"[{extras}]" - requirement += " @ {}".format(self._url) + requirement += f" @ {self._url}" + + if self.directory: + requirement += f"#subdirectory={self.directory}" return requirement - def is_url(self): # type: () -> bool + def is_url(self) -> bool: return True - - def with_constraint(self, constraint): # type: ("BaseConstraint") -> URLDependency - new = URLDependency( - self.pretty_name, - url=self._url, - optional=self.is_optional(), - category=self.category, - extras=self._extras, - ) - - new._constraint = constraint - new._pretty_constraint = str(constraint) - - new.is_root = self.is_root - new.python_versions = self.python_versions - new.marker = self.marker - new.transitive_marker = self.transitive_marker - - for in_extra in self.in_extras: - new.in_extras.append(in_extra) - - return new - - def __str__(self): # type: () -> str - return "{} ({} url)".format(self._pretty_name, self._pretty_constraint) - - def __hash__(self): # type: () -> int - return hash((self._name, self._url)) diff --git a/conda_lock/_vendor/poetry/core/packages/utils/link.py b/conda_lock/_vendor/poetry/core/packages/utils/link.py index 76f6c1c78..5be0b7411 100644 --- a/conda_lock/_vendor/poetry/core/packages/utils/link.py +++ b/conda_lock/_vendor/poetry/core/packages/utils/link.py @@ -1,40 +1,40 @@ +from __future__ import annotations + import posixpath import re +import urllib.parse as urlparse -from typing import TYPE_CHECKING -from typing import Any -from typing import Optional -from typing import Tuple - - -if TYPE_CHECKING: - from pip._internal.index.collector import HTMLPage # noqa - -from .utils import path_to_url -from .utils import splitext - - -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +from conda_lock._vendor.poetry.core.packages.utils.utils import path_to_url +from conda_lock._vendor.poetry.core.packages.utils.utils import splitext class Link: def __init__( - self, url, comes_from=None, requires_python=None - ): # type: (str, Optional["HTMLPage"], Optional[str]) -> None + self, + url: str, + requires_python: str | None = None, + metadata: str | bool | None = None, + yanked: str | bool = False, + ) -> None: """ Object representing a parsed link from https://pypi.python.org/simple/* url: url of the resource pointed to (href of the link) - comes_from: - instance of HTMLPage where the link was found, or string. requires_python: String containing the `Requires-Python` metadata field, specified in PEP 345. This may be specified by a data-requires-python attribute in the HTML link tag, as described in PEP 503. + metadata: + String of the syntax `=` representing the hash + of the Core Metadata file. This may be specified by a + data-dist-info-metadata attribute in the HTML link tag, as described + in PEP 658. + yanked: + False, if the data-yanked attribute is not present. + A string, if the data-yanked attribute has a string value. + True, if the data-yanked attribute is present but has no value. + According to PEP 592. """ # url can be a UNC windows share @@ -42,91 +42,96 @@ def __init__( url = path_to_url(url) self.url = url - self.comes_from = comes_from self.requires_python = requires_python if requires_python else None - def __str__(self): # type: () -> str + if isinstance(metadata, str): + metadata = {"true": True, "": False, "false": False}.get( + metadata.strip().lower(), metadata + ) + + self._metadata = metadata + self._yanked = yanked + + def __str__(self) -> str: if self.requires_python: - rp = " (requires-python:%s)" % self.requires_python + rp = f" (requires-python:{self.requires_python})" else: rp = "" - if self.comes_from: - return "%s (from %s)%s" % (self.url, self.comes_from, rp) - else: - return str(self.url) - def __repr__(self): # type: () -> str - return "" % self + return f"{self.url}{rp}" - def __eq__(self, other): # type: (Any) -> bool + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: if not isinstance(other, Link): return NotImplemented return self.url == other.url - def __ne__(self, other): # type: (Any) -> bool + def __ne__(self, other: object) -> bool: if not isinstance(other, Link): return NotImplemented return self.url != other.url - def __lt__(self, other): # type: (Any) -> bool + def __lt__(self, other: object) -> bool: if not isinstance(other, Link): return NotImplemented return self.url < other.url - def __le__(self, other): # type: (Any) -> bool + def __le__(self, other: object) -> bool: if not isinstance(other, Link): return NotImplemented return self.url <= other.url - def __gt__(self, other): # type: (Any) -> bool + def __gt__(self, other: object) -> bool: if not isinstance(other, Link): return NotImplemented return self.url > other.url - def __ge__(self, other): # type: (Any) -> bool + def __ge__(self, other: object) -> bool: if not isinstance(other, Link): return NotImplemented return self.url >= other.url - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(self.url) @property - def filename(self): # type: () -> str + def filename(self) -> str: _, netloc, path, _, _ = urlparse.urlsplit(self.url) name = posixpath.basename(path.rstrip("/")) or netloc name = urlparse.unquote(name) - assert name, "URL %r produced no filename" % self.url + return name @property - def scheme(self): # type: () -> str + def scheme(self) -> str: return urlparse.urlsplit(self.url)[0] @property - def netloc(self): # type: () -> str + def netloc(self) -> str: return urlparse.urlsplit(self.url)[1] @property - def path(self): # type: () -> str + def path(self) -> str: return urlparse.unquote(urlparse.urlsplit(self.url)[2]) - def splitext(self): # type: () -> Tuple[str, str] + def splitext(self) -> tuple[str, str]: return splitext(posixpath.basename(self.path.rstrip("/"))) @property - def ext(self): # type: () -> str + def ext(self) -> str: return self.splitext()[1] @property - def url_without_fragment(self): # type: () -> str + def url_without_fragment(self) -> str: scheme, netloc, path, query, fragment = urlparse.urlsplit(self.url) return urlparse.urlunsplit((scheme, netloc, path, query, None)) _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)") @property - def egg_fragment(self): # type: () -> Optional[str] + def egg_fragment(self) -> str | None: match = self._egg_fragment_re.search(self.url) if not match: return None @@ -135,7 +140,7 @@ def egg_fragment(self): # type: () -> Optional[str] _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)") @property - def subdirectory_fragment(self): # type: () -> Optional[str] + def subdirectory_fragment(self) -> str | None: match = self._subdirectory_fragment_re.search(self.url) if not match: return None @@ -144,41 +149,69 @@ def subdirectory_fragment(self): # type: () -> Optional[str] _hash_re = re.compile(r"(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)") @property - def hash(self): # type: () -> Optional[str] + def has_metadata(self) -> bool: + if self._metadata is None: + return False + return bool(self._metadata) and (self.is_wheel or self.is_sdist) + + @property + def metadata_url(self) -> str | None: + if self.has_metadata: + return f"{self.url_without_fragment.split('?', 1)[0]}.metadata" + return None + + @property + def metadata_hash(self) -> str | None: + if self.has_metadata and isinstance(self._metadata, str): + match = self._hash_re.search(self._metadata) + if match: + return match.group(2) + return None + + @property + def metadata_hash_name(self) -> str | None: + if self.has_metadata and isinstance(self._metadata, str): + match = self._hash_re.search(self._metadata) + if match: + return match.group(1) + return None + + @property + def hash(self) -> str | None: match = self._hash_re.search(self.url) if match: return match.group(2) return None @property - def hash_name(self): # type: () -> Optional[str] + def hash_name(self) -> str | None: match = self._hash_re.search(self.url) if match: return match.group(1) return None @property - def show_url(self): # type: () -> str + def show_url(self) -> str: return posixpath.basename(self.url.split("#", 1)[0].split("?", 1)[0]) @property - def is_wheel(self): # type: () -> bool + def is_wheel(self) -> bool: return self.ext == ".whl" @property - def is_wininst(self): # type: () -> bool + def is_wininst(self) -> bool: return self.ext == ".exe" @property - def is_egg(self): # type: () -> bool + def is_egg(self) -> bool: return self.ext == ".egg" @property - def is_sdist(self): # type: () -> bool + def is_sdist(self) -> bool: return self.ext in {".tar.bz2", ".tar.gz", ".zip"} @property - def is_artifact(self): # type: () -> bool + def is_artifact(self) -> bool: """ Determines if this points to an actual artifact (e.g. a tarball) or if it points to an "abstract" thing like a path or a VCS location. @@ -187,3 +220,13 @@ def is_artifact(self): # type: () -> bool return False return True + + @property + def yanked(self) -> bool: + return isinstance(self._yanked, str) or bool(self._yanked) + + @property + def yanked_reason(self) -> str: + if isinstance(self._yanked, str): + return self._yanked + return "" diff --git a/conda_lock/_vendor/poetry/core/packages/utils/utils.py b/conda_lock/_vendor/poetry/core/packages/utils/utils.py index e49782c67..daefdd66e 100644 --- a/conda_lock/_vendor/poetry/core/packages/utils/utils.py +++ b/conda_lock/_vendor/poetry/core/packages/utils/utils.py @@ -1,47 +1,45 @@ -import os +from __future__ import annotations + +import functools import posixpath import re import sys +from pathlib import Path from typing import TYPE_CHECKING from typing import Dict from typing import List from typing import Tuple -from typing import Union - -from six.moves.urllib.parse import unquote # noqa -from six.moves.urllib.parse import urlsplit # noqa -from six.moves.urllib.request import url2pathname # noqa - -from conda_lock._vendor.poetry.core.packages.constraints.constraint import Constraint -from conda_lock._vendor.poetry.core.packages.constraints.multi_constraint import MultiConstraint -from conda_lock._vendor.poetry.core.packages.constraints.union_constraint import UnionConstraint -from conda_lock._vendor.poetry.core.semver import EmptyConstraint -from conda_lock._vendor.poetry.core.semver import Version -from conda_lock._vendor.poetry.core.semver import VersionConstraint -from conda_lock._vendor.poetry.core.semver import VersionRange -from conda_lock._vendor.poetry.core.semver import VersionUnion -from conda_lock._vendor.poetry.core.semver import parse_constraint -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.version.markers import BaseMarker -from conda_lock._vendor.poetry.core.version.markers import MarkerUnion -from conda_lock._vendor.poetry.core.version.markers import MultiMarker -from conda_lock._vendor.poetry.core.version.markers import SingleMarker +from urllib.parse import unquote +from urllib.parse import urlsplit +from urllib.request import url2pathname + +from conda_lock._vendor.poetry.core.constraints.version import Version +from conda_lock._vendor.poetry.core.constraints.version import VersionRange +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint +from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML +from conda_lock._vendor.poetry.core.version.markers import dnf if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.packages.constraints import BaseConstraint # noqa - from conda_lock._vendor.poetry.core.semver import VersionTypes # noqa + from conda_lock._vendor.poetry.core.constraints.generic import BaseConstraint + from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint + from conda_lock._vendor.poetry.core.version.markers import BaseMarker + + # Even though we've `from __future__ import annotations`, mypy doesn't seem to like + # this as `dict[str, ...]` + ConvertedMarkers = Dict[str, List[List[Tuple[str, str]]]] + BZ2_EXTENSIONS = (".tar.bz2", ".tbz") XZ_EXTENSIONS = (".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma") ZIP_EXTENSIONS = (".zip", ".whl") TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar") ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS -SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS +SUPPORTED_EXTENSIONS: tuple[str, ...] = ZIP_EXTENSIONS + TAR_EXTENSIONS try: - import bz2 # noqa + import bz2 # noqa: F401 SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS except ImportError: @@ -49,14 +47,14 @@ try: # Only for Python 3.3+ - import lzma # noqa + import lzma # noqa: F401 SUPPORTED_EXTENSIONS += XZ_EXTENSIONS except ImportError: pass -def path_to_url(path): # type: (Union[str, Path]) -> str +def path_to_url(path: str | Path) -> str: """ Convert a path to a file: URL. The path will be made absolute unless otherwise specified and have quoted path parts. @@ -64,7 +62,7 @@ def path_to_url(path): # type: (Union[str, Path]) -> str return Path(path).absolute().as_uri() -def url_to_path(url): # type: (str) -> Path +def url_to_path(url: str) -> Path: """ Convert an RFC8089 file URI to path. @@ -72,7 +70,7 @@ def url_to_path(url): # type: (str) -> Path https://github.com/pypa/pip/blob/4d1932fcdd1974c820ea60b3286984ebb0c3beaa/src/pip/_internal/utils/urls.py#L31 """ if not url.startswith("file:"): - raise ValueError("{} is not a valid file URI".format(url)) + raise ValueError(f"{url} is not a valid file URI") _, netloc, path, _, _ = urlsplit(url) @@ -84,13 +82,13 @@ def url_to_path(url): # type: (str) -> Path netloc = "\\\\" + netloc else: raise ValueError( - "non-local file URIs are not supported on this platform: {}".format(url) + f"non-local file URIs are not supported on this platform: {url}" ) return Path(url2pathname(netloc + unquote(path))) -def is_url(name): # type: (str) -> bool +def is_url(name: str) -> bool: if ":" not in name: return False scheme = name.split(":", 1)[0].lower() @@ -110,7 +108,7 @@ def is_url(name): # type: (str) -> bool ] -def strip_extras(path): # type: (str) -> Tuple[str, str] +def strip_extras(path: str) -> tuple[str, str | None]: m = re.match(r"^(.+)(\[[^\]]+\])$", path) extras = None if m: @@ -122,17 +120,25 @@ def strip_extras(path): # type: (str) -> Tuple[str, str] return path_no_extras, extras -def is_installable_dir(path): # type: (str) -> bool - """Return True if `path` is a directory containing a setup.py file.""" - if not os.path.isdir(path): +@functools.lru_cache(maxsize=None) +def is_python_project(path: Path) -> bool: + """Return true if the directory is a Python project""" + if not path.is_dir(): return False - setup_py = os.path.join(path, "setup.py") - if os.path.isfile(setup_py): - return True - return False + setup_py = path / "setup.py" + setup_cfg = path / "setup.cfg" + setuptools_project = setup_py.exists() or setup_cfg.exists() + + pyproject = PyProjectTOML(path / "pyproject.toml") + + supports_pep517 = setuptools_project or pyproject.is_build_system_defined() + supports_poetry = pyproject.is_poetry_project() -def is_archive_file(name): # type: (str) -> bool + return supports_pep517 or supports_poetry + + +def is_archive_file(name: str) -> bool: """Return True if `name` is a considered as an archive file.""" ext = splitext(name)[1].lower() if ext in ARCHIVE_EXTENSIONS: @@ -140,7 +146,7 @@ def is_archive_file(name): # type: (str) -> bool return False -def splitext(path): # type: (str) -> Tuple[str, str] +def splitext(path: str) -> tuple[str, str]: """Like os.path.splitext, but take off .tar too""" base, ext = posixpath.splitext(path) if base.lower().endswith(".tar"): @@ -149,149 +155,151 @@ def splitext(path): # type: (str) -> Tuple[str, str] return base, ext -def group_markers( - markers, or_=False -): # type: (List[BaseMarker], bool) -> List[Union[Tuple[str, str, str], List[Tuple[str, str, str]]]] - groups = [[]] - - for marker in markers: - if or_: - groups.append([]) - - if isinstance(marker, (MultiMarker, MarkerUnion)): - groups[-1].append( - group_markers(marker.markers, isinstance(marker, MarkerUnion)) - ) - elif isinstance(marker, SingleMarker): - lhs, op, rhs = marker.name, marker.operator, marker.value - - groups[-1].append((lhs, op, rhs)) - - return groups - +def convert_markers(marker: BaseMarker) -> ConvertedMarkers: + from conda_lock._vendor.poetry.core.version.markers import MarkerUnion + from conda_lock._vendor.poetry.core.version.markers import MultiMarker + from conda_lock._vendor.poetry.core.version.markers import SingleMarker + + requirements: ConvertedMarkers = {} + marker = dnf(marker) + conjunctions = marker.markers if isinstance(marker, MarkerUnion) else [marker] + group_count = len(conjunctions) + + def add_constraint( + marker_name: str, constraint: tuple[str, str], group_index: int + ) -> None: + # python_full_version is equivalent to python_version + # for Poetry so we merge them + if marker_name == "python_full_version": + marker_name = "python_version" + if marker_name not in requirements: + requirements[marker_name] = [[] for _ in range(group_count)] + requirements[marker_name][group_index].append(constraint) + + for i, sub_marker in enumerate(conjunctions): + if isinstance(sub_marker, MultiMarker): + for m in sub_marker.markers: + if isinstance(m, SingleMarker): + add_constraint(m.name, (m.operator, m.value), i) + elif isinstance(sub_marker, SingleMarker): + add_constraint(sub_marker.name, (sub_marker.operator, sub_marker.value), i) + + for group_name in requirements: + # remove duplicates + seen = [] + for r in requirements[group_name]: + if r not in seen: + seen.append(r) + requirements[group_name] = seen -def convert_markers(marker): # type: (BaseMarker) -> Dict[str, List[Tuple[str, str]]] - groups = group_markers([marker]) - - requirements = {} - - def _group( - _groups, or_=False - ): # type: (List[Union[Tuple[str, str, str], List[Tuple[str, str, str]]]], bool) -> None - ors = {} - for group in _groups: - if isinstance(group, list): - _group(group, or_=True) - else: - variable, op, value = group - group_name = str(variable) - - # python_full_version is equivalent to python_version - # for Poetry so we merge them - if group_name == "python_full_version": - group_name = "python_version" - - if group_name not in requirements: - requirements[group_name] = [] - - if group_name not in ors: - ors[group_name] = or_ - - if ors[group_name] or not requirements[group_name]: - requirements[group_name].append([]) - - requirements[group_name][-1].append((str(op), str(value))) - - ors[group_name] = False + return requirements - _group(groups, or_=True) - return requirements +def contains_group_without_marker(markers: ConvertedMarkers, marker_name: str) -> bool: + return marker_name not in markers or [] in markers[marker_name] def create_nested_marker( - name, constraint -): # type: (str, Union["BaseConstraint", VersionUnion, Version, VersionConstraint]) -> str + name: str, + constraint: BaseConstraint | VersionConstraint, +) -> str: + from conda_lock._vendor.poetry.core.constraints.generic import Constraint + from conda_lock._vendor.poetry.core.constraints.generic import MultiConstraint + from conda_lock._vendor.poetry.core.constraints.generic import UnionConstraint + from conda_lock._vendor.poetry.core.constraints.version import VersionUnion + if constraint.is_any(): return "" if isinstance(constraint, (MultiConstraint, UnionConstraint)): - parts = [] + multi_parts = [] for c in constraint.constraints: - multi = False - if isinstance(c, (MultiConstraint, UnionConstraint)): - multi = True - - parts.append((multi, create_nested_marker(name, c))) + multi = isinstance(c, (MultiConstraint, UnionConstraint)) + multi_parts.append((multi, create_nested_marker(name, c))) glue = " and " if isinstance(constraint, UnionConstraint): - parts = ["({})".format(part[1]) if part[0] else part[1] for part in parts] + parts = [f"({part[1]})" if part[0] else part[1] for part in multi_parts] glue = " or " else: - parts = [part[1] for part in parts] + parts = [part[1] for part in multi_parts] marker = glue.join(parts) elif isinstance(constraint, Constraint): - marker = '{} {} "{}"'.format(name, constraint.operator, constraint.version) + marker = f'{name} {constraint.operator} "{constraint.version}"' elif isinstance(constraint, VersionUnion): - parts = [] - for c in constraint.ranges: - parts.append(create_nested_marker(name, c)) - + parts = [create_nested_marker(name, c) for c in constraint.ranges] glue = " or " - parts = ["({})".format(part) for part in parts] - + parts = [f"({part})" for part in parts] marker = glue.join(parts) elif isinstance(constraint, Version): if name == "python_version" and constraint.precision >= 3: name = "python_full_version" - marker = '{} == "{}"'.format(name, constraint.text) + marker = f'{name} == "{constraint.text}"' else: - if constraint.min is not None: - op = ">=" - if not constraint.include_min: - op = ">" - - version = constraint.min - if constraint.max is not None: - min_name = max_name = name - if min_name == "python_version" and constraint.min.precision >= 3: - min_name = "python_full_version" - - if max_name == "python_version" and constraint.max.precision >= 3: - max_name = "python_full_version" + assert isinstance(constraint, VersionRange) + min_name = max_name = name - text = '{} {} "{}"'.format(min_name, op, version) - - op = "<=" - if not constraint.include_max: - op = "<" - - version = constraint.max + parts = [] - text += ' and {} {} "{}"'.format(max_name, op, version) + # `python_version` is a special case: to keep the constructed marker equivalent + # to the constraint we need to be careful with the precision. + # + # PEP 440 tells us that when we come to make the comparison the release + # segment will be zero padded: eg "<= 3.10" is equivalent to "<= 3.10.0". + # + # But "python_version <= 3.10" is _not_ equivalent to "python_version <= 3.10.0" + # - see normalize_python_version_markers. + # + # A similar issue arises for a constraint like "> 3.6". + if constraint.min is not None: + op = ">=" if constraint.include_min else ">" + version = constraint.min + if min_name == "python_version" and version.precision >= 3: + min_name = "python_full_version" + + if ( + min_name == "python_version" + and not constraint.include_min + and version.precision < 3 + ): + padding = ".0" * (3 - version.precision) + part = f'python_full_version > "{version}{padding}"' + else: + part = f'{min_name} {op} "{version}"' - return text - elif constraint.max is not None: - op = "<=" - if not constraint.include_max: - op = "<" + parts.append(part) + if constraint.max is not None: + op = "<=" if constraint.include_max else "<" version = constraint.max - else: - return "" + if max_name == "python_version" and version.precision >= 3: + max_name = "python_full_version" + + if ( + max_name == "python_version" + and constraint.include_max + and version.precision < 3 + ): + padding = ".0" * (3 - version.precision) + part = f'python_full_version <= "{version}{padding}"' + else: + part = f'{max_name} {op} "{version}"' - if name == "python_version" and version.precision >= 3: - name = "python_full_version" + parts.append(part) - marker = '{} {} "{}"'.format(name, op, version) + marker = " and ".join(parts) return marker -def get_python_constraint_from_marker(marker,): # type: (BaseMarker) -> "VersionTypes" +def get_python_constraint_from_marker( + marker: BaseMarker, +) -> VersionConstraint: + from conda_lock._vendor.poetry.core.constraints.version import EmptyConstraint + from conda_lock._vendor.poetry.core.constraints.version import VersionRange + python_marker = marker.only("python_version", "python_full_version") if python_marker.is_any(): return VersionRange() @@ -300,33 +308,64 @@ def get_python_constraint_from_marker(marker,): # type: (BaseMarker) -> "Versio return EmptyConstraint() markers = convert_markers(marker) + if contains_group_without_marker(markers, "python_version"): + # groups are in disjunctive normal form (DNF), + # an empty group means that python_version does not appear in this group, + # which means that python_version is arbitrary for this group + return VersionRange() + python_version_markers = markers["python_version"] + normalized = normalize_python_version_markers(python_version_markers) + constraint = parse_constraint(normalized) + return constraint + + +def normalize_python_version_markers( # NOSONAR + disjunction: list[list[tuple[str, str]]], +) -> str: ors = [] - for or_ in markers["python_version"]: + for or_ in disjunction: ands = [] for op, version in or_: # Expand python version - if op == "==": + if op == "==" and "*" not in version and version.count(".") < 2: version = "~" + version op = "" - elif op == "!=": + + elif op == "!=" and "*" not in version and version.count(".") < 2: version += ".*" + elif op in ("<=", ">"): + # Make adjustments on encountering versions with less than full + # precision. + # + # Per PEP-508: + # python_version <-> '.'.join(platform.python_version_tuple()[:2]) + # + # So for two digits of precision we make the following adjustments: + # - `python_version > "x.y"` requires version >= x.(y+1).anything + # - `python_version <= "x.y"` requires version < x.(y+1).anything + # + # Treatment when we see a single digit of precision is less clear: is + # that even a legitimate marker? + # + # Experiment suggests that pip behaviour is essentially to make a + # lexicographical comparison, for example `python_version > "3"` is + # satisfied by version 3.anything, whereas `python_version <= "3"` is + # satisfied only by version 2.anything. + # + # We achieve the above by fiddling with the operator and version in the + # marker. parsed_version = Version.parse(version) - if parsed_version.precision == 1: + if parsed_version.precision < 3: 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 + + if parsed_version.precision == 2: + version = parsed_version.next_minor().text + elif op in ("in", "not in"): versions = [] for v in re.split("[ ,]+", version): @@ -339,14 +378,14 @@ def get_python_constraint_from_marker(marker,): # type: (BaseMarker) -> "Versio versions.append(op_ + ".".join(split)) - glue = " || " if op == "in" else ", " if versions: + glue = " || " if op == "in" else ", " ands.append(glue.join(versions)) continue - ands.append("{}{}".format(op, version)) + ands.append(f"{op}{version}") ors.append(" ".join(ands)) - return parse_constraint(" || ".join(ors)) + return " || ".join(ors) diff --git a/conda_lock/_vendor/poetry/core/packages/vcs_dependency.py b/conda_lock/_vendor/poetry/core/packages/vcs_dependency.py index 6b3d7b53c..c55f8c7e2 100644 --- a/conda_lock/_vendor/poetry/core/packages/vcs_dependency.py +++ b/conda_lock/_vendor/poetry/core/packages/vcs_dependency.py @@ -1,16 +1,8 @@ -from typing import TYPE_CHECKING -from typing import FrozenSet -from typing import List -from typing import Optional -from typing import Union +from __future__ import annotations -from conda_lock._vendor.poetry.core.vcs import git +from typing import Iterable -from .dependency import Dependency - - -if TYPE_CHECKING: - from .constraints import BaseConstraint +from conda_lock._vendor.poetry.core.packages.dependency import Dependency class VCSDependency(Dependency): @@ -20,146 +12,114 @@ class VCSDependency(Dependency): def __init__( self, - name, # type: str - vcs, # type: str - source, # type: str - branch=None, # type: Optional[str] - tag=None, # type: Optional[str] - rev=None, # type: Optional[str] - resolved_rev=None, # type: Optional[str] - category="main", # type: str - optional=False, # type: bool - develop=False, # type: bool - extras=None, # type: Union[List[str], FrozenSet[str]] - ): + name: str, + vcs: str, + source: str, + branch: str | None = None, + tag: str | None = None, + rev: str | None = None, + resolved_rev: str | None = None, + directory: str | None = None, + groups: Iterable[str] | None = None, + optional: bool = False, + develop: bool = False, + extras: Iterable[str] | None = None, + ) -> None: self._vcs = vcs self._source = source - if not any([branch, tag, rev]): - # If nothing has been specified, we assume master - branch = "master" - self._branch = branch self._tag = tag self._rev = rev + self._directory = directory self._develop = develop - super(VCSDependency, self).__init__( + super().__init__( name, "*", - category=category, + groups=groups, optional=optional, allows_prereleases=True, source_type=self._vcs.lower(), source_url=self._source, - source_reference=branch or tag or rev, + source_reference=branch or tag or rev or "HEAD", source_resolved_reference=resolved_rev, + source_subdirectory=directory, extras=extras, ) @property - def vcs(self): # type: () -> str + def vcs(self) -> str: return self._vcs @property - def source(self): # type: () -> str + def source(self) -> str: return self._source @property - def branch(self): # type: () -> Optional[str] + def branch(self) -> str | None: return self._branch @property - def tag(self): # type: () -> Optional[str] + def tag(self) -> str | None: return self._tag @property - def rev(self): # type: () -> Optional[str] + def rev(self) -> str | None: return self._rev @property - def develop(self): # type: () -> bool + def directory(self) -> str | None: + return self._directory + + @property + def develop(self) -> bool: return self._develop @property - def reference(self): # type: () -> str - return self._branch or self._tag or self._rev + def reference(self) -> str: + reference = self._branch or self._tag or self._rev or "" + return reference @property - def pretty_constraint(self): # type: () -> str + def pretty_constraint(self) -> str: if self._branch: what = "branch" version = self._branch elif self._tag: what = "tag" version = self._tag - else: + elif self._rev: what = "rev" version = self._rev + else: + return "" - return "{} {}".format(what, version) + return f"{what} {version}" @property - def base_pep_508_name(self): # type: () -> str + def base_pep_508_name(self) -> str: + from conda_lock._vendor.poetry.core.vcs import git + requirement = self.pretty_name parsed_url = git.ParsedUrl.parse(self._source) if self.extras: - requirement += "[{}]".format(",".join(self.extras)) + extras = ",".join(sorted(self.extras)) + requirement += f"[{extras}]" if parsed_url.protocol is not None: - requirement += " @ {}+{}@{}".format(self._vcs, self._source, self.reference) + requirement += f" @ {self._vcs}+{self._source}" else: - requirement += " @ {}+ssh://{}@{}".format( - self._vcs, parsed_url.format(), self.reference - ) - - return requirement + requirement += f" @ {self._vcs}+ssh://{parsed_url.format()}" - def is_vcs(self): # type: () -> bool - return True + if self.reference: + requirement += f"@{self.reference}" - def accepts_prereleases(self): # type: () -> bool - return True + if self._directory: + requirement += f"#subdirectory={self._directory}" - def with_constraint(self, constraint): # type: ("BaseConstraint") -> VCSDependency - new = VCSDependency( - self.pretty_name, - self._vcs, - self._source, - branch=self._branch, - tag=self._tag, - rev=self._rev, - resolved_rev=self._source_resolved_reference, - optional=self.is_optional(), - category=self.category, - develop=self._develop, - extras=self._extras, - ) - - new._constraint = constraint - new._pretty_constraint = str(constraint) - - new.is_root = self.is_root - new.python_versions = self.python_versions - new.marker = self.marker - new.transitive_marker = self.transitive_marker - - for in_extra in self.in_extras: - new.in_extras.append(in_extra) - - return new - - def __str__(self): # type: () -> str - reference = self._vcs - if self._branch: - reference += " branch {}".format(self._branch) - elif self._tag: - reference += " tag {}".format(self._tag) - elif self._rev: - reference += " rev {}".format(self._rev) - - return "{} ({} {})".format(self._pretty_name, self._constraint, reference) + return requirement - def __hash__(self): # type: () -> int - return hash((self._name, self._vcs, self._branch, self._tag, self._rev)) + def is_vcs(self) -> bool: + return True diff --git a/conda_lock/_vendor/poetry/core/poetry.py b/conda_lock/_vendor/poetry/core/poetry.py index a096cbf60..2f8901e9a 100644 --- a/conda_lock/_vendor/poetry/core/poetry.py +++ b/conda_lock/_vendor/poetry/core/poetry.py @@ -1,41 +1,45 @@ -from __future__ import absolute_import -from __future__ import unicode_literals +from __future__ import annotations from typing import TYPE_CHECKING from typing import Any -from conda_lock._vendor.poetry.core.pyproject import PyProjectTOML -from conda_lock._vendor.poetry.core.utils._compat import Path # noqa - if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.packages import ProjectPackage # noqa - from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOMLFile # noqa + from pathlib import Path + + from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage + from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML + from conda_lock._vendor.poetry.core.toml import TOMLFile -class Poetry(object): +class Poetry: def __init__( - self, file, local_config, package, - ): # type: (Path, dict, "ProjectPackage") -> None + self, + file: Path, + local_config: dict[str, Any], + package: ProjectPackage, + ) -> None: + from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML + self._pyproject = PyProjectTOML(file) self._package = package self._local_config = local_config @property - def pyproject(self): # type: () -> PyProjectTOML + def pyproject(self) -> PyProjectTOML: return self._pyproject @property - def file(self): # type: () -> "PyProjectTOMLFile" + def file(self) -> TOMLFile: return self._pyproject.file @property - def package(self): # type: () -> "ProjectPackage" + def package(self) -> ProjectPackage: return self._package @property - def local_config(self): # type: () -> dict + def local_config(self) -> dict[str, Any]: return self._local_config - def get_project_config(self, config, default=None): # type: (str, Any) -> Any + def get_project_config(self, config: str, default: Any = None) -> Any: return self._local_config.get("config", {}).get(config, default) diff --git a/conda_lock/_vendor/poetry/core/py.typed b/conda_lock/_vendor/poetry/core/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/conda_lock/_vendor/poetry/core/pyproject/__init__.py b/conda_lock/_vendor/poetry/core/pyproject/__init__.py index b8b677521..e69de29bb 100644 --- a/conda_lock/_vendor/poetry/core/pyproject/__init__.py +++ b/conda_lock/_vendor/poetry/core/pyproject/__init__.py @@ -1,6 +0,0 @@ -from conda_lock._vendor.poetry.core.pyproject.exceptions import PyProjectException -from conda_lock._vendor.poetry.core.pyproject.tables import BuildSystem -from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML - - -__all__ = [clazz.__name__ for clazz in {BuildSystem, PyProjectException, PyProjectTOML}] diff --git a/conda_lock/_vendor/poetry/core/pyproject/exceptions.py b/conda_lock/_vendor/poetry/core/pyproject/exceptions.py index 07eea82ee..9cdbb65b3 100644 --- a/conda_lock/_vendor/poetry/core/pyproject/exceptions.py +++ b/conda_lock/_vendor/poetry/core/pyproject/exceptions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from conda_lock._vendor.poetry.core.exceptions import PoetryCoreException diff --git a/conda_lock/_vendor/poetry/core/pyproject/tables.py b/conda_lock/_vendor/poetry/core/pyproject/tables.py index 1225a6c0e..25db6763f 100644 --- a/conda_lock/_vendor/poetry/core/pyproject/tables.py +++ b/conda_lock/_vendor/poetry/core/pyproject/tables.py @@ -1,57 +1,50 @@ -from typing import TYPE_CHECKING -from typing import List -from typing import Optional +from __future__ import annotations -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils.helpers import canonicalize_name +from contextlib import suppress +from pathlib import Path +from typing import TYPE_CHECKING if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.packages import Dependency # noqa + from conda_lock._vendor.poetry.core.packages.dependency import Dependency # TODO: Convert to dataclass once python 2.7, 3.5 is dropped class BuildSystem: def __init__( - self, build_backend=None, requires=None - ): # type: (Optional[str], Optional[List[str]]) -> None + self, build_backend: str | None = None, requires: list[str] | None = None + ) -> None: self.build_backend = ( build_backend if build_backend is not None else "setuptools.build_meta:__legacy__" ) self.requires = requires if requires is not None else ["setuptools", "wheel"] - self._dependencies = None + self._dependencies: list[Dependency] | None = None @property - def dependencies(self): # type: () -> List["Dependency"] + def dependencies(self) -> list[Dependency]: if self._dependencies is None: # avoid circular dependency when loading DirectoryDependency - from conda_lock._vendor.poetry.core.packages import DirectoryDependency - from conda_lock._vendor.poetry.core.packages import FileDependency - from conda_lock._vendor.poetry.core.packages import dependency_from_pep_508 + from conda_lock._vendor.poetry.core.packages.dependency import Dependency + from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency + from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency self._dependencies = [] for requirement in self.requires: dependency = None try: - dependency = dependency_from_pep_508(requirement) + dependency = Dependency.create_from_pep_508(requirement) except ValueError: # PEP 517 requires can be path if not PEP 508 path = Path(requirement) - try: + # compatibility Python < 3.8 + # https://docs.python.org/3/library/pathlib.html#methods + with suppress(OSError): if path.is_file(): - dependency = FileDependency( - name=canonicalize_name(path.name), path=path - ) + dependency = FileDependency(name=path.name, path=path) elif path.is_dir(): - dependency = DirectoryDependency( - name=canonicalize_name(path.name), path=path - ) - except OSError: - # compatibility Python < 3.8 - # https://docs.python.org/3/library/pathlib.html#methods - pass + dependency = DirectoryDependency(name=path.name, path=path) if dependency is None: # skip since we could not determine requirement diff --git a/conda_lock/_vendor/poetry/core/pyproject/toml.py b/conda_lock/_vendor/poetry/core/pyproject/toml.py index c9fa6e60d..d944f4eff 100644 --- a/conda_lock/_vendor/poetry/core/pyproject/toml.py +++ b/conda_lock/_vendor/poetry/core/pyproject/toml.py @@ -1,38 +1,51 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING from typing import Any -from typing import Optional -from typing import Union -from tomlkit.container import Container -from tomlkit.toml_document import TOMLDocument +from tomlkit.api import table + + +if TYPE_CHECKING: + from pathlib import Path -from conda_lock._vendor.poetry.core.pyproject.exceptions import PyProjectException -from conda_lock._vendor.poetry.core.pyproject.tables import BuildSystem -from conda_lock._vendor.poetry.core.toml import TOMLFile -from conda_lock._vendor.poetry.core.utils._compat import Path + from tomlkit.toml_document import TOMLDocument + + from conda_lock._vendor.poetry.core.pyproject.tables import BuildSystem + from conda_lock._vendor.poetry.core.toml import TOMLFile class PyProjectTOML: - def __init__(self, path): # type: (Union[str, Path]) -> None + def __init__(self, path: str | Path) -> None: + from conda_lock._vendor.poetry.core.toml import TOMLFile + self._file = TOMLFile(path=path) - self._data = None # type: Optional[TOMLDocument] - self._build_system = None # type: Optional[BuildSystem] - self._poetry_config = None # type: Optional[TOMLDocument] + self._data: TOMLDocument | None = None + self._build_system: BuildSystem | None = None @property - def file(self): # type: () -> TOMLFile + def file(self) -> TOMLFile: return self._file @property - def data(self): # type: () -> TOMLDocument + def data(self) -> TOMLDocument: + from tomlkit.toml_document import TOMLDocument + if self._data is None: if not self._file.exists(): self._data = TOMLDocument() else: self._data = self._file.read() + return self._data + def is_build_system_defined(self) -> bool: + return self._file.exists() and "build-system" in self.data + @property - def build_system(self): # type: () -> BuildSystem + def build_system(self) -> BuildSystem: + from conda_lock._vendor.poetry.core.pyproject.tables import BuildSystem + if self._build_system is None: build_backend = None requires = None @@ -46,45 +59,56 @@ def build_system(self): # type: () -> BuildSystem build_backend=container.get("build-backend", build_backend), requires=container.get("requires", requires), ) + return self._build_system @property - def poetry_config(self): # type: () -> Optional[TOMLDocument] - if self._poetry_config is None: - self._poetry_config = self.data.get("tool", {}).get("poetry") - if self._poetry_config is None: - raise PyProjectException( - "[tool.poetry] section not found in {}".format(self._file) - ) - return self._poetry_config - - def is_poetry_project(self): # type: () -> bool + def poetry_config(self) -> dict[str, Any]: + from tomlkit.exceptions import NonExistentKey + + try: + tool = self.data["tool"] + assert isinstance(tool, dict) + config = tool["poetry"] + assert isinstance(config, dict) + return config + except NonExistentKey as e: + from conda_lock._vendor.poetry.core.pyproject.exceptions import PyProjectException + + raise PyProjectException( + f"[tool.poetry] section not found in {self._file}" + ) from e + + def is_poetry_project(self) -> bool: + from conda_lock._vendor.poetry.core.pyproject.exceptions import PyProjectException + if self.file.exists(): try: _ = self.poetry_config return True except PyProjectException: pass + return False - def __getattr__(self, item): # type: (str) -> Any + def __getattr__(self, item: str) -> Any: return getattr(self.data, item) - def save(self): # type: () -> None + def save(self) -> None: data = self.data - if self._poetry_config is not None: - data["tool"]["poetry"] = self._poetry_config - if self._build_system is not None: if "build-system" not in data: - data["build-system"] = Container() - data["build-system"]["requires"] = self._build_system.requires - data["build-system"]["build-backend"] = self._build_system.build_backend + data["build-system"] = table() + + build_system = data["build-system"] + assert isinstance(build_system, dict) + + build_system["requires"] = self._build_system.requires + build_system["build-backend"] = self._build_system.build_backend self.file.write(data=data) - def reload(self): # type: () -> None + def reload(self) -> None: self._data = None self._build_system = None - self._poetry_config = None diff --git a/conda_lock/_vendor/poetry/core/semver/__init__.py b/conda_lock/_vendor/poetry/core/semver/__init__.py index 2cff22d6e..be3955935 100644 --- a/conda_lock/_vendor/poetry/core/semver/__init__.py +++ b/conda_lock/_vendor/poetry/core/semver/__init__.py @@ -1,151 +1,10 @@ -import re +from __future__ import annotations -from typing import Union +import warnings -from .empty_constraint import EmptyConstraint -from .exceptions import ParseConstraintError -from .patterns import BASIC_CONSTRAINT -from .patterns import CARET_CONSTRAINT -from .patterns import TILDE_CONSTRAINT -from .patterns import TILDE_PEP440_CONSTRAINT -from .patterns import X_CONSTRAINT -from .version import Version -from .version_constraint import VersionConstraint -from .version_range import VersionRange -from .version_union import VersionUnion - -VersionTypes = Union[Version, VersionRange, VersionUnion, EmptyConstraint] - - -def parse_constraint(constraints): # type: (str) -> VersionTypes - if constraints == "*": - return VersionRange() - - or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip()) - or_groups = [] - for constraints in or_constraints: - and_constraints = re.split( - "(?< ,]) *(? 1: - for constraint in and_constraints: - constraint_objects.append(parse_single_constraint(constraint)) - else: - constraint_objects.append(parse_single_constraint(and_constraints[0])) - - if len(constraint_objects) == 1: - constraint = constraint_objects[0] - else: - constraint = constraint_objects[0] - for next_constraint in constraint_objects[1:]: - constraint = constraint.intersect(next_constraint) - - or_groups.append(constraint) - - if len(or_groups) == 1: - return or_groups[0] - else: - return VersionUnion.of(*or_groups) - - -def parse_single_constraint(constraint): # type: (str) -> VersionTypes - m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint) - if m: - return VersionRange() - - # Tilde range - m = TILDE_CONSTRAINT.match(constraint) - if m: - version = Version.parse(m.group(1)) - - high = version.stable.next_minor - if len(m.group(1).split(".")) == 1: - high = version.stable.next_major - - return VersionRange(version, high, include_min=True) - - # PEP 440 Tilde range (~=) - m = TILDE_PEP440_CONSTRAINT.match(constraint) - if m: - precision = 1 - if m.group(3): - precision += 1 - - if m.group(4): - precision += 1 - - version = Version.parse(m.group(1)) - - if precision == 2: - high = version.stable.next_major - else: - high = version.stable.next_minor - - return VersionRange(version, high, include_min=True) - - # Caret range - m = CARET_CONSTRAINT.match(constraint) - if m: - version = Version.parse(m.group(1)) - - return VersionRange(version, version.next_breaking, include_min=True) - - # X Range - m = X_CONSTRAINT.match(constraint) - if m: - op = m.group(1) - major = int(m.group(2)) - minor = m.group(3) - - if minor is not None: - version = Version(major, int(minor), 0) - - result = VersionRange(version, version.next_minor, include_min=True) - else: - if major == 0: - result = VersionRange(max=Version(1, 0, 0)) - else: - version = Version(major, 0, 0) - - result = VersionRange(version, version.next_major, include_min=True) - - if op == "!=": - result = VersionRange().difference(result) - - return result - - # Basic comparator - m = BASIC_CONSTRAINT.match(constraint) - if m: - op = m.group(1) - version = m.group(2) - - if version == "dev": - version = "0.0-dev" - - try: - version = Version.parse(version) - except ValueError: - raise ValueError( - "Could not parse version constraint: {}".format(constraint) - ) - - if op == "<": - return VersionRange(max=version) - elif op == "<=": - return VersionRange(max=version, include_max=True) - elif op == ">": - return VersionRange(min=version) - elif op == ">=": - return VersionRange(min=version, include_min=True) - elif op == "!=": - return VersionUnion(VersionRange(max=version), VersionRange(min=version)) - else: - return version - - raise ParseConstraintError( - "Could not parse version constraint: {}".format(constraint) - ) +warnings.warn( + "poetry.core.semver is deprecated. Use poetry.core.constraints.version instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/conda_lock/_vendor/poetry/core/semver/empty_constraint.py b/conda_lock/_vendor/poetry/core/semver/empty_constraint.py index c463fa586..1dc2b3394 100644 --- a/conda_lock/_vendor/poetry/core/semver/empty_constraint.py +++ b/conda_lock/_vendor/poetry/core/semver/empty_constraint.py @@ -1,37 +1,6 @@ -from typing import TYPE_CHECKING +from __future__ import annotations -from .version_constraint import VersionConstraint +from conda_lock._vendor.poetry.core.constraints.version import EmptyConstraint -if TYPE_CHECKING: - from . import VersionTypes # noqa - from .version import Version # noqa - - -class EmptyConstraint(VersionConstraint): - def is_empty(self): # type: () -> bool - return True - - def is_any(self): # type: () -> bool - return False - - def allows(self, version): # type: ("Version") -> bool - return False - - def allows_all(self, other): # type: ("VersionTypes") -> bool - return other.is_empty() - - def allows_any(self, other): # type: ("VersionTypes") -> bool - return False - - def intersect(self, other): # type: ("VersionTypes") -> EmptyConstraint - return self - - def union(self, other): # type: ("VersionTypes") -> "VersionTypes" - return other - - def difference(self, other): # type: ("VersionTypes") -> EmptyConstraint - return self - - def __str__(self): # type: () -> str - return "" +__all__ = ["EmptyConstraint"] diff --git a/conda_lock/_vendor/poetry/core/semver/exceptions.py b/conda_lock/_vendor/poetry/core/semver/exceptions.py index b24323997..25ee01e36 100644 --- a/conda_lock/_vendor/poetry/core/semver/exceptions.py +++ b/conda_lock/_vendor/poetry/core/semver/exceptions.py @@ -1,6 +1,6 @@ -class ParseVersionError(ValueError): - pass +from __future__ import annotations +from conda_lock._vendor.poetry.core.constraints.version.exceptions import ParseConstraintError -class ParseConstraintError(ValueError): - pass + +__all__ = ["ParseConstraintError"] diff --git a/conda_lock/_vendor/poetry/core/semver/helpers.py b/conda_lock/_vendor/poetry/core/semver/helpers.py new file mode 100644 index 000000000..cfb80c3d7 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/semver/helpers.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.version.parser import parse_constraint +from conda_lock._vendor.poetry.core.constraints.version.parser import parse_single_constraint + + +__all__ = ["parse_constraint", "parse_single_constraint"] diff --git a/conda_lock/_vendor/poetry/core/semver/patterns.py b/conda_lock/_vendor/poetry/core/semver/patterns.py index 6cda2a305..4d411d5ea 100644 --- a/conda_lock/_vendor/poetry/core/semver/patterns.py +++ b/conda_lock/_vendor/poetry/core/semver/patterns.py @@ -1,22 +1,18 @@ -import re +from __future__ import annotations +from conda_lock._vendor.poetry.core.constraints.version.patterns import BASIC_CONSTRAINT +from conda_lock._vendor.poetry.core.constraints.version.patterns import CARET_CONSTRAINT +from conda_lock._vendor.poetry.core.constraints.version.patterns import COMPLETE_VERSION +from conda_lock._vendor.poetry.core.constraints.version.patterns import TILDE_CONSTRAINT +from conda_lock._vendor.poetry.core.constraints.version.patterns import TILDE_PEP440_CONSTRAINT +from conda_lock._vendor.poetry.core.constraints.version.patterns import X_CONSTRAINT -MODIFIERS = ( - "[._-]?" - r"((?!post)(?:beta|b|c|pre|RC|alpha|a|patch|pl|p|dev)(?:(?:[.-]?\d+)*)?)?" - r"([+-]?([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?" -) -_COMPLETE_VERSION = r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?{}(?:\+[^\s]+)?".format( - MODIFIERS -) - -COMPLETE_VERSION = re.compile("(?i)" + _COMPLETE_VERSION) - -CARET_CONSTRAINT = re.compile(r"(?i)^\^({})$".format(_COMPLETE_VERSION)) -TILDE_CONSTRAINT = re.compile(r"(?i)^~(?!=)\s*({})$".format(_COMPLETE_VERSION)) -TILDE_PEP440_CONSTRAINT = re.compile(r"(?i)^~=\s*({})$".format(_COMPLETE_VERSION)) -X_CONSTRAINT = re.compile(r"^(!=|==)?\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.[xX*])+$") -BASIC_CONSTRAINT = re.compile( - r"(?i)^(<>|!=|>=?|<=?|==?)?\s*({}|dev)".format(_COMPLETE_VERSION) -) +__all__ = [ + "COMPLETE_VERSION", + "CARET_CONSTRAINT", + "TILDE_CONSTRAINT", + "TILDE_PEP440_CONSTRAINT", + "X_CONSTRAINT", + "BASIC_CONSTRAINT", +] diff --git a/conda_lock/_vendor/poetry/core/semver/util.py b/conda_lock/_vendor/poetry/core/semver/util.py new file mode 100644 index 000000000..fc00c0ec8 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/semver/util.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.version import constraint_regions + + +__all__ = ["constraint_regions"] diff --git a/conda_lock/_vendor/poetry/core/semver/version.py b/conda_lock/_vendor/poetry/core/semver/version.py index acd5f3e84..2ac78c33d 100644 --- a/conda_lock/_vendor/poetry/core/semver/version.py +++ b/conda_lock/_vendor/poetry/core/semver/version.py @@ -1,476 +1,6 @@ -import re +from __future__ import annotations -from typing import TYPE_CHECKING -from typing import List -from typing import Optional -from typing import Union +from conda_lock._vendor.poetry.core.constraints.version import Version -from .empty_constraint import EmptyConstraint -from .exceptions import ParseVersionError -from .patterns import COMPLETE_VERSION -from .version_constraint import VersionConstraint -from .version_range import VersionRange -from .version_union import VersionUnion - -if TYPE_CHECKING: - from . import VersionTypes # noqa - - -class Version(VersionRange): - """ - A parsed semantic version number. - """ - - def __init__( - self, - major, # type: int - minor=None, # type: Optional[int] - patch=None, # type: Optional[int] - rest=None, # type: Optional[int] - pre=None, # type: Optional[str] - build=None, # type: Optional[str] - text=None, # type: Optional[str] - precision=None, # type: Optional[int] - ): # type: (...) -> None - self._major = int(major) - self._precision = None - if precision is None: - self._precision = 1 - - if minor is None: - minor = 0 - else: - if self._precision is not None: - self._precision += 1 - - self._minor = int(minor) - - if patch is None: - patch = 0 - else: - if self._precision is not None: - self._precision += 1 - - if rest is None: - rest = 0 - else: - if self._precision is not None: - self._precision += 1 - - if precision is not None: - self._precision = precision - - self._patch = int(patch) - self._rest = int(rest) - - if text is None: - parts = [str(major)] - if self._precision >= 2 or minor != 0: - parts.append(str(minor)) - - if self._precision >= 3 or patch != 0: - parts.append(str(patch)) - - if self._precision >= 4 or rest != 0: - parts.append(str(rest)) - - text = ".".join(parts) - if pre: - text += "-{}".format(pre) - - if build: - text += "+{}".format(build) - - self._text = text - - pre = self._normalize_prerelease(pre) - - self._prerelease = [] - if pre is not None: - self._prerelease = self._split_parts(pre) - - build = self._normalize_build(build) - - self._build = [] - if build is not None: - if build.startswith(("-", "+")): - build = build[1:] - - self._build = self._split_parts(build) - - @property - def major(self): # type: () -> int - return self._major - - @property - def minor(self): # type: () -> int - return self._minor - - @property - def patch(self): # type: () -> int - return self._patch - - @property - def rest(self): # type: () -> int - return self._rest - - @property - def prerelease(self): # type: () -> List[str] - return self._prerelease - - @property - def build(self): # type: () -> List[str] - return self._build - - @property - def text(self): # type: () -> str - return self._text - - @property - def precision(self): # type: () -> int - return self._precision - - @property - def stable(self): # type: () -> Version - if not self.is_prerelease(): - return self - - return self.next_patch - - @property - def next_major(self): # type: () -> Version - if self.is_prerelease() and self.minor == 0 and self.patch == 0: - return Version(self.major, self.minor, self.patch) - - return self._increment_major() - - @property - def next_minor(self): # type: () -> Version - if self.is_prerelease() and self.patch == 0: - return Version(self.major, self.minor, self.patch) - - return self._increment_minor() - - @property - def next_patch(self): # type: () -> Version - if self.is_prerelease(): - return Version(self.major, self.minor, self.patch) - - return self._increment_patch() - - @property - def next_breaking(self): # type: () -> Version - if self.major == 0: - if self.minor != 0: - return self._increment_minor() - - if self._precision == 1: - return self._increment_major() - elif self._precision == 2: - return self._increment_minor() - - return self._increment_patch() - - return self._increment_major() - - @property - def first_prerelease(self): # type: () -> Version - return Version.parse( - "{}.{}.{}-alpha.0".format(self.major, self.minor, self.patch) - ) - - @property - def min(self): # type: () -> Version - return self - - @property - def max(self): # type: () -> Version - return self - - @property - def full_max(self): # type: () -> Version - return self - - @property - def include_min(self): # type: () -> bool - return True - - @property - def include_max(self): # type: () -> bool - return True - - @classmethod - def parse(cls, text): # type: (str) -> Version - try: - match = COMPLETE_VERSION.match(text) - except TypeError: - match = None - - if match is None: - raise ParseVersionError('Unable to parse "{}".'.format(text)) - - text = text.rstrip(".") - - major = int(match.group(1)) - minor = int(match.group(2)) if match.group(2) else None - patch = int(match.group(3)) if match.group(3) else None - rest = int(match.group(4)) if match.group(4) else None - - pre = match.group(5) - build = match.group(6) - - if build: - build = build.lstrip("+") - - return Version(major, minor, patch, rest, pre, build, text) - - def is_any(self): # type: () -> bool - return False - - def is_empty(self): # type: () -> bool - return False - - def is_prerelease(self): # type: () -> bool - return len(self._prerelease) > 0 - - def allows(self, version): # type: (Version) -> bool - return self == version - - def allows_all(self, other): # type: ("VersionTypes") -> bool - return other.is_empty() or other == self - - def allows_any(self, other): # type: ("VersionTypes") -> bool - return other.allows(self) - - def intersect( - self, other - ): # type: ("VersionTypes") -> Union[Version, EmptyConstraint] - if other.allows(self): - return self - - return EmptyConstraint() - - def union(self, other): # type: ("VersionTypes") -> "VersionTypes" - from .version_range import VersionRange - - if other.allows(self): - return other - - if isinstance(other, VersionRange): - if other.min == self: - return VersionRange( - other.min, - other.max, - include_min=True, - include_max=other.include_max, - ) - - if other.max == self: - return VersionRange( - other.min, - other.max, - include_min=other.include_min, - include_max=True, - ) - - return VersionUnion.of(self, other) - - def difference( - self, other - ): # type: ("VersionTypes") -> Union[Version, EmptyConstraint] - if other.allows(self): - return EmptyConstraint() - - return self - - def equals_without_prerelease(self, other): # type: (Version) -> bool - return ( - self.major == other.major - and self.minor == other.minor - and self.patch == other.patch - ) - - def _increment_major(self): # type: () -> Version - return Version(self.major + 1, 0, 0, precision=self._precision) - - def _increment_minor(self): # type: () -> Version - return Version(self.major, self.minor + 1, 0, precision=self._precision) - - def _increment_patch(self): # type: () -> Version - return Version( - self.major, self.minor, self.patch + 1, precision=self._precision - ) - - def _normalize_prerelease(self, pre): # type: (str) -> Optional[str] - if not pre: - return - - m = re.match(r"(?i)^(a|alpha|b|beta|c|pre|rc|dev)[-.]?(\d+)?$", pre) - if not m: - return - - modifier = m.group(1) - number = m.group(2) - - if number is None: - number = 0 - - if modifier == "a": - modifier = "alpha" - elif modifier == "b": - modifier = "beta" - elif modifier in {"c", "pre"}: - modifier = "rc" - elif modifier == "dev": - modifier = "alpha" - - return "{}.{}".format(modifier, number) - - def _normalize_build(self, build): # type: (str) -> Optional[str] - if not build: - return - - if build.startswith("post"): - build = build.lstrip("post") - - if not build: - return - - return build - - def _split_parts(self, text): # type: (str) -> List[Union[str, int]] - parts = text.split(".") - - for i, part in enumerate(parts): - try: - parts[i] = int(part) - except (TypeError, ValueError): - continue - - return parts - - def __lt__(self, other): # type: (Version) -> int - return self._cmp(other) < 0 - - def __le__(self, other): # type: (Version) -> int - return self._cmp(other) <= 0 - - def __gt__(self, other): # type: (Version) -> int - return self._cmp(other) > 0 - - def __ge__(self, other): # type: (Version) -> int - return self._cmp(other) >= 0 - - def _cmp(self, other): # type: (Version) -> int - if not isinstance(other, VersionConstraint): - return NotImplemented - - if not isinstance(other, Version): - return -other._cmp(self) - - if self.major != other.major: - return self._cmp_parts(self.major, other.major) - - if self.minor != other.minor: - return self._cmp_parts(self.minor, other.minor) - - if self.patch != other.patch: - return self._cmp_parts(self.patch, other.patch) - - if self.rest != other.rest: - return self._cmp_parts(self.rest, other.rest) - - # Pre-releases always come before no pre-release string. - if not self.is_prerelease() and other.is_prerelease(): - return 1 - - if not other.is_prerelease() and self.is_prerelease(): - return -1 - - comparison = self._cmp_lists(self.prerelease, other.prerelease) - if comparison != 0: - return comparison - - # Builds always come after no build string. - if not self.build and other.build: - return -1 - - if not other.build and self.build: - return 1 - - return self._cmp_lists(self.build, other.build) - - def _cmp_parts(self, a, b): # type: (Optional[int], Optional[int]) -> int - if a < b: - return -1 - elif a > b: - return 1 - - return 0 - - def _cmp_lists(self, a, b): # type: (List, List) -> int - for i in range(max(len(a), len(b))): - a_part = None - if i < len(a): - a_part = a[i] - - b_part = None - if i < len(b): - b_part = b[i] - - if a_part == b_part: - continue - - # Missing parts come after present ones. - if a_part is None: - return -1 - - if b_part is None: - return 1 - - if isinstance(a_part, int): - if isinstance(b_part, int): - return self._cmp_parts(a_part, b_part) - - return -1 - else: - if isinstance(b_part, int): - return 1 - - return self._cmp_parts(a_part, b_part) - - return 0 - - def __eq__(self, other): # type: (Version) -> bool - if not isinstance(other, Version): - return NotImplemented - - return ( - self._major == other.major - and self._minor == other.minor - and self._patch == other.patch - and self._rest == other.rest - and self._prerelease == other.prerelease - and self._build == other.build - ) - - def __ne__(self, other): # type: ("VersionTypes") -> bool - return not self == other - - def __str__(self): # type: () -> str - return self._text - - def __repr__(self): # type: () -> str - return "".format(str(self)) - - def __hash__(self): # type: () -> int - return hash( - ( - self.major, - self.minor, - self.patch, - ".".join(str(p) for p in self.prerelease), - ".".join(str(p) for p in self.build), - ) - ) +__all__ = ["Version"] diff --git a/conda_lock/_vendor/poetry/core/semver/version_constraint.py b/conda_lock/_vendor/poetry/core/semver/version_constraint.py index 4e5f73e4f..30b5e416e 100644 --- a/conda_lock/_vendor/poetry/core/semver/version_constraint.py +++ b/conda_lock/_vendor/poetry/core/semver/version_constraint.py @@ -1,31 +1,6 @@ -from typing import TYPE_CHECKING +from __future__ import annotations +from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint -if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.semver import Version # noqa - -class VersionConstraint: - def is_empty(self): # type: () -> bool - raise NotImplementedError() - - def is_any(self): # type: () -> bool - raise NotImplementedError() - - def allows(self, version): # type: ("Version") -> bool - raise NotImplementedError() - - def allows_all(self, other): # type: (VersionConstraint) -> bool - raise NotImplementedError() - - def allows_any(self, other): # type: (VersionConstraint) -> bool - raise NotImplementedError() - - def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint - raise NotImplementedError() - - def union(self, other): # type: (VersionConstraint) -> VersionConstraint - raise NotImplementedError() - - def difference(self, other): # type: (VersionConstraint) -> VersionConstraint - raise NotImplementedError() +__all__ = ["VersionConstraint"] diff --git a/conda_lock/_vendor/poetry/core/semver/version_range.py b/conda_lock/_vendor/poetry/core/semver/version_range.py index ead2a45ce..e193a037a 100644 --- a/conda_lock/_vendor/poetry/core/semver/version_range.py +++ b/conda_lock/_vendor/poetry/core/semver/version_range.py @@ -1,465 +1,6 @@ -from typing import TYPE_CHECKING -from typing import Any -from typing import List -from typing import Optional +from __future__ import annotations -from .empty_constraint import EmptyConstraint -from .version_constraint import VersionConstraint -from .version_union import VersionUnion +from conda_lock._vendor.poetry.core.constraints.version import VersionRange -if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.semver.version import Version - - from . import VersionTypes # noqa - - -class VersionRange(VersionConstraint): - def __init__( - self, - min=None, # type: Optional["Version"] - max=None, # type: Optional["Version"] - include_min=False, # type: bool - include_max=False, # type: bool - always_include_max_prerelease=False, # type: bool - ): - full_max = max - if ( - not always_include_max_prerelease - and not include_max - and full_max is not None - and not full_max.is_prerelease() - and not full_max.build - and ( - min is None - or not min.is_prerelease() - or not min.equals_without_prerelease(full_max) - ) - ): - full_max = full_max.first_prerelease - - self._min = min - self._max = max - self._full_max = full_max - self._include_min = include_min - self._include_max = include_max - - @property - def min(self): # type: () -> "Version" - return self._min - - @property - def max(self): # type: () -> "Version" - return self._max - - @property - def full_max(self): # type: () -> "Version" - return self._full_max - - @property - def include_min(self): # type: () -> bool - return self._include_min - - @property - def include_max(self): # type: () -> bool - return self._include_max - - def is_empty(self): # type: () -> bool - return False - - def is_any(self): # type: () -> bool - return self._min is None and self._max is None - - def allows(self, other): # type: ("Version") -> bool - if self._min is not None: - if other < self._min: - return False - - if not self._include_min and other == self._min: - return False - - if self.full_max is not None: - if other > self.full_max: - return False - - if not self._include_max and other == self.full_max: - return False - - return True - - def allows_all(self, other): # type: ("VersionTypes") -> bool - from .version import Version - - if other.is_empty(): - return True - - if isinstance(other, Version): - return self.allows(other) - - if isinstance(other, VersionUnion): - return all([self.allows_all(constraint) for constraint in other.ranges]) - - if isinstance(other, VersionRange): - return not other.allows_lower(self) and not other.allows_higher(self) - - raise ValueError("Unknown VersionConstraint type {}.".format(other)) - - def allows_any(self, other): # type: ("VersionTypes") -> bool - from .version import Version - - if other.is_empty(): - return False - - if isinstance(other, Version): - return self.allows(other) - - if isinstance(other, VersionUnion): - return any([self.allows_any(constraint) for constraint in other.ranges]) - - if isinstance(other, VersionRange): - return not other.is_strictly_lower(self) and not other.is_strictly_higher( - self - ) - - raise ValueError("Unknown VersionConstraint type {}.".format(other)) - - def intersect(self, other): # type: ("VersionTypes") -> "VersionTypes" - from .version import Version - - if other.is_empty(): - return other - - if isinstance(other, VersionUnion): - return other.intersect(self) - - # A range and a Version just yields the version if it's in the range. - if isinstance(other, Version): - if self.allows(other): - return other - - return EmptyConstraint() - - if not isinstance(other, VersionRange): - raise ValueError("Unknown VersionConstraint type {}.".format(other)) - - if self.allows_lower(other): - if self.is_strictly_lower(other): - return EmptyConstraint() - - intersect_min = other.min - intersect_include_min = other.include_min - else: - if other.is_strictly_lower(self): - return EmptyConstraint() - - intersect_min = self._min - intersect_include_min = self._include_min - - if self.allows_higher(other): - intersect_max = other.max - intersect_include_max = other.include_max - else: - intersect_max = self._max - intersect_include_max = self._include_max - - if intersect_min is None and intersect_max is None: - return VersionRange() - - # If the range is just a single version. - if intersect_min == intersect_max: - # Because we already verified that the lower range isn't strictly - # lower, there must be some overlap. - assert intersect_include_min and intersect_include_max - - return intersect_min - - # If we got here, there is an actual range. - return VersionRange( - intersect_min, intersect_max, intersect_include_min, intersect_include_max - ) - - def union(self, other): # type: ("VersionTypes") -> "VersionTypes" - from .version import Version - - if isinstance(other, Version): - if self.allows(other): - return self - - if other == self.min: - return VersionRange( - self.min, self.max, include_min=True, include_max=self.include_max - ) - - if other == self.max: - return VersionRange( - self.min, self.max, include_min=self.include_min, include_max=True - ) - - return VersionUnion.of(self, other) - - if isinstance(other, VersionRange): - # If the two ranges don't overlap, we won't be able to create a single - # VersionRange for both of them. - edges_touch = ( - self.max == other.min and (self.include_max or other.include_min) - ) or (self.min == other.max and (self.include_min or other.include_max)) - - if not edges_touch and not self.allows_any(other): - return VersionUnion.of(self, other) - - if self.allows_lower(other): - union_min = self.min - union_include_min = self.include_min - else: - union_min = other.min - union_include_min = other.include_min - - if self.allows_higher(other): - union_max = self.max - union_include_max = self.include_max - else: - union_max = other.max - union_include_max = other.include_max - - return VersionRange( - union_min, - union_max, - include_min=union_include_min, - include_max=union_include_max, - ) - - return VersionUnion.of(self, other) - - def difference(self, other): # type: ("VersionTypes") -> "VersionTypes" - from .version import Version - - if other.is_empty(): - return self - - if isinstance(other, Version): - if not self.allows(other): - return self - - if other == self.min: - if not self.include_min: - return self - - return VersionRange(self.min, self.max, False, self.include_max) - - if other == self.max: - if not self.include_max: - return self - - return VersionRange(self.min, self.max, self.include_min, False) - - return VersionUnion.of( - VersionRange(self.min, other, self.include_min, False), - VersionRange(other, self.max, False, self.include_max), - ) - elif isinstance(other, VersionRange): - if not self.allows_any(other): - return self - - if not self.allows_lower(other): - before = None - elif self.min == other.min: - before = self.min - else: - before = VersionRange( - self.min, other.min, self.include_min, not other.include_min - ) - - if not self.allows_higher(other): - after = None - elif self.max == other.max: - after = self.max - else: - after = VersionRange( - other.max, self.max, not other.include_max, self.include_max - ) - - if before is None and after is None: - return EmptyConstraint() - - if before is None: - return after - - if after is None: - return before - - return VersionUnion.of(before, after) - elif isinstance(other, VersionUnion): - ranges = [] # type: List[VersionRange] - current = self - - for range in other.ranges: - # Skip any ranges that are strictly lower than [current]. - if range.is_strictly_lower(current): - continue - - # If we reach a range strictly higher than [current], no more ranges - # will be relevant so we can bail early. - if range.is_strictly_higher(current): - break - - difference = current.difference(range) - if difference.is_empty(): - return EmptyConstraint() - elif isinstance(difference, VersionUnion): - # If [range] split [current] in half, we only need to continue - # checking future ranges against the latter half. - ranges.append(difference.ranges[0]) - current = difference.ranges[-1] - else: - current = difference - - if not ranges: - return current - - return VersionUnion.of(*(ranges + [current])) - - raise ValueError("Unknown VersionConstraint type {}.".format(other)) - - def allows_lower(self, other): # type: (VersionRange) -> bool - if self.min is None: - return other.min is not None - - if other.min is None: - return False - - if self.min < other.min: - return True - - if self.min > other.min: - return False - - return self.include_min and not other.include_min - - def allows_higher(self, other): # type: (VersionRange) -> bool - if self.full_max is None: - return other.max is not None - - if other.full_max is None: - return False - - if self.full_max < other.full_max: - return False - - if self.full_max > other.full_max: - return True - - return self.include_max and not other.include_max - - def is_strictly_lower(self, other): # type: (VersionRange) -> bool - if self.full_max is None or other.min is None: - return False - - if self.full_max < other.min: - return True - - if self.full_max > other.min: - return False - - return not self.include_max or not other.include_min - - def is_strictly_higher(self, other): # type: (VersionRange) -> bool - return other.is_strictly_lower(self) - - def is_adjacent_to(self, other): # type: (VersionRange) -> bool - if self.max != other.min: - return False - - return ( - self.include_max - and not other.include_min - or not self.include_max - and other.include_min - ) - - def __eq__(self, other): # type: (Any) -> int - if not isinstance(other, VersionRange): - return False - - return ( - self._min == other.min - and self._max == other.max - and self._include_min == other.include_min - and self._include_max == other.include_max - ) - - def __lt__(self, other): # type: (VersionRange) -> int - return self._cmp(other) < 0 - - def __le__(self, other): # type: (VersionRange) -> int - return self._cmp(other) <= 0 - - def __gt__(self, other): # type: (VersionRange) -> int - return self._cmp(other) > 0 - - def __ge__(self, other): # type: (VersionRange) -> int - return self._cmp(other) >= 0 - - def _cmp(self, other): # type: (VersionRange) -> int - if self.min is None: - if other.min is None: - return self._compare_max(other) - - return -1 - elif other.min is None: - return 1 - - result = self.min._cmp(other.min) - if result != 0: - return result - - if self.include_min != other.include_min: - return -1 if self.include_min else 1 - - return self._compare_max(other) - - def _compare_max(self, other): # type: (VersionRange) -> int - if self.max is None: - if other.max is None: - return 0 - - return 1 - elif other.max is None: - return -1 - - result = self.max._cmp(other.max) - if result != 0: - return result - - if self.include_max != other.include_max: - return 1 if self.include_max else -1 - - return 0 - - def __str__(self): # type: () -> str - text = "" - - if self.min is not None: - text += ">=" if self.include_min else ">" - text += self.min.text - - if self.max is not None: - if self.min is not None: - text += "," - - text += "{}{}".format("<=" if self.include_max else "<", self.max.text) - - if self.min is None and self.max is None: - return "*" - - return text - - def __repr__(self): # type: () -> str - return "".format(str(self)) - - def __hash__(self): # type: () -> int - return ( - hash(self.min) - ^ hash(self.max) - ^ hash(self.include_min) - ^ hash(self.include_max) - ) +__all__ = ["VersionRange"] diff --git a/conda_lock/_vendor/poetry/core/semver/version_range_constraint.py b/conda_lock/_vendor/poetry/core/semver/version_range_constraint.py new file mode 100644 index 000000000..c6d7bda81 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/semver/version_range_constraint.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.constraints.version import VersionRangeConstraint + + +__all__ = ["VersionRangeConstraint"] diff --git a/conda_lock/_vendor/poetry/core/semver/version_union.py b/conda_lock/_vendor/poetry/core/semver/version_union.py index 50a597db6..e3af2d7a9 100644 --- a/conda_lock/_vendor/poetry/core/semver/version_union.py +++ b/conda_lock/_vendor/poetry/core/semver/version_union.py @@ -1,268 +1,6 @@ -from typing import TYPE_CHECKING -from typing import Any -from typing import List +from __future__ import annotations -from .empty_constraint import EmptyConstraint -from .version_constraint import VersionConstraint +from conda_lock._vendor.poetry.core.constraints.version import VersionUnion -if TYPE_CHECKING: - from . import VersionTypes # noqa - from .version import Version - from .version_range import VersionRange - - -class VersionUnion(VersionConstraint): - """ - A version constraint representing a union of multiple disjoint version - ranges. - - An instance of this will only be created if the version can't be represented - as a non-compound value. - """ - - def __init__(self, *ranges): # type: (*"VersionRange") -> None - self._ranges = list(ranges) - - @property - def ranges(self): # type: () -> List["VersionRange"] - return self._ranges - - @classmethod - def of(cls, *ranges): # type: (*"VersionTypes") -> "VersionTypes" - from .version_range import VersionRange - - flattened = [] - for constraint in ranges: - if constraint.is_empty(): - continue - - if isinstance(constraint, VersionUnion): - flattened += constraint.ranges - continue - - flattened.append(constraint) - - if not flattened: - return EmptyConstraint() - - if any([constraint.is_any() for constraint in flattened]): - return VersionRange() - - # Only allow Versions and VersionRanges here so we can more easily reason - # about everything in flattened. _EmptyVersions and VersionUnions are - # filtered out above. - for constraint in flattened: - if isinstance(constraint, VersionRange): - continue - - raise ValueError("Unknown VersionConstraint type {}.".format(constraint)) - - flattened.sort() - - merged = [] - for constraint in flattened: - # Merge this constraint with the previous one, but only if they touch. - if not merged or ( - not merged[-1].allows_any(constraint) - and not merged[-1].is_adjacent_to(constraint) - ): - merged.append(constraint) - else: - merged[-1] = merged[-1].union(constraint) - - if len(merged) == 1: - return merged[0] - - return VersionUnion(*merged) - - def is_empty(self): # type: () -> bool - return False - - def is_any(self): # type: () -> bool - return False - - def allows(self, version): # type: ("Version") -> bool - return any([constraint.allows(version) for constraint in self._ranges]) - - def allows_all(self, other): # type: ("VersionTypes") -> bool - our_ranges = iter(self._ranges) - their_ranges = iter(self._ranges_for(other)) - - our_current_range = next(our_ranges, None) - their_current_range = next(their_ranges, None) - - while our_current_range and their_current_range: - if our_current_range.allows_all(their_current_range): - their_current_range = next(their_ranges, None) - else: - our_current_range = next(our_ranges, None) - - return their_current_range is None - - def allows_any(self, other): # type: ("VersionTypes") -> bool - our_ranges = iter(self._ranges) - their_ranges = iter(self._ranges_for(other)) - - our_current_range = next(our_ranges, None) - their_current_range = next(their_ranges, None) - - while our_current_range and their_current_range: - if our_current_range.allows_any(their_current_range): - return True - - if their_current_range.allows_higher(our_current_range): - our_current_range = next(our_ranges, None) - else: - their_current_range = next(their_ranges, None) - - return False - - def intersect(self, other): # type: ("VersionTypes") -> "VersionTypes" - our_ranges = iter(self._ranges) - their_ranges = iter(self._ranges_for(other)) - new_ranges = [] - - our_current_range = next(our_ranges, None) - their_current_range = next(their_ranges, None) - - while our_current_range and their_current_range: - intersection = our_current_range.intersect(their_current_range) - - if not intersection.is_empty(): - new_ranges.append(intersection) - - if their_current_range.allows_higher(our_current_range): - our_current_range = next(our_ranges, None) - else: - their_current_range = next(their_ranges, None) - - return VersionUnion.of(*new_ranges) - - def union(self, other): # type: ("VersionTypes") -> "VersionTypes" - return VersionUnion.of(self, other) - - def difference(self, other): # type: ("VersionTypes") -> "VersionTypes" - our_ranges = iter(self._ranges) - their_ranges = iter(self._ranges_for(other)) - new_ranges = [] - - state = { - "current": next(our_ranges, None), - "their_range": next(their_ranges, None), - } - - def their_next_range(): # type: () -> bool - state["their_range"] = next(their_ranges, None) - if state["their_range"]: - return True - - new_ranges.append(state["current"]) - our_current = next(our_ranges, None) - while our_current: - new_ranges.append(our_current) - our_current = next(our_ranges, None) - - return False - - def our_next_range(include_current=True): # type: (bool) -> bool - if include_current: - new_ranges.append(state["current"]) - - our_current = next(our_ranges, None) - if not our_current: - return False - - state["current"] = our_current - - return True - - while True: - if state["their_range"] is None: - break - - if state["their_range"].is_strictly_lower(state["current"]): - if not their_next_range(): - break - - continue - - if state["their_range"].is_strictly_higher(state["current"]): - if not our_next_range(): - break - - continue - - difference = state["current"].difference(state["their_range"]) - if isinstance(difference, VersionUnion): - assert len(difference.ranges) == 2 - new_ranges.append(difference.ranges[0]) - state["current"] = difference.ranges[-1] - - if not their_next_range(): - break - elif difference.is_empty(): - if not our_next_range(False): - break - else: - state["current"] = difference - - if state["current"].allows_higher(state["their_range"]): - if not their_next_range(): - break - else: - if not our_next_range(): - break - - if not new_ranges: - return EmptyConstraint() - - if len(new_ranges) == 1: - return new_ranges[0] - - return VersionUnion.of(*new_ranges) - - def _ranges_for(self, constraint): # type: ("VersionTypes") -> List["VersionRange"] - from .version_range import VersionRange - - if constraint.is_empty(): - return [] - - if isinstance(constraint, VersionUnion): - return constraint.ranges - - if isinstance(constraint, VersionRange): - return [constraint] - - raise ValueError("Unknown VersionConstraint type {}".format(constraint)) - - def excludes_single_version(self): # type: () -> bool - from .version import Version - from .version_range import VersionRange - - return isinstance(VersionRange().difference(self), Version) - - def __eq__(self, other): # type: (Any) -> bool - if not isinstance(other, VersionUnion): - return False - - return self._ranges == other.ranges - - def __hash__(self): # type: () -> int - h = hash(self._ranges[0]) - - for range in self._ranges[1:]: - h ^= hash(range) - - return h - - def __str__(self): # type: () -> str - from .version_range import VersionRange - - if self.excludes_single_version(): - return "!={}".format(VersionRange().difference(self)) - - return " || ".join([str(r) for r in self._ranges]) - - def __repr__(self): # type: () -> str - return "".format(str(self)) +__all__ = ["VersionUnion"] diff --git a/conda_lock/_vendor/poetry/core/spdx/__init__.py b/conda_lock/_vendor/poetry/core/spdx/__init__.py index 713aa30df..e69de29bb 100644 --- a/conda_lock/_vendor/poetry/core/spdx/__init__.py +++ b/conda_lock/_vendor/poetry/core/spdx/__init__.py @@ -1,57 +0,0 @@ -import json -import os - -from io import open -from typing import Dict -from typing import Optional - -from .license import License -from .updater import Updater - - -_licenses = None # type: Optional[Dict[str, License]] - - -def license_by_id(identifier): # type: (str) -> License - if _licenses is None: - load_licenses() - - id = identifier.lower() - - if id not in _licenses: - if not identifier: - raise ValueError("A license identifier is required") - return License(identifier, identifier, False, False) - - return _licenses[id] - - -def load_licenses(): # type: () -> None - global _licenses - - _licenses = {} - - licenses_file = os.path.join(os.path.dirname(__file__), "data", "licenses.json") - - with open(licenses_file, encoding="utf-8") as f: - data = json.loads(f.read()) - - for name, license_info in data.items(): - license = License(name, license_info[0], license_info[1], license_info[2]) - _licenses[name.lower()] = license - - full_name = license_info[0].lower() - if full_name in _licenses: - existing_license = _licenses[full_name] - if not existing_license.is_deprecated: - continue - - _licenses[full_name] = license - - # Add a Proprietary license for non-standard licenses - _licenses["proprietary"] = License("Proprietary", "Proprietary", False, False) - - -if __name__ == "__main__": - updater = Updater() - updater.dump() diff --git a/conda_lock/_vendor/poetry/core/spdx/data/licenses.json b/conda_lock/_vendor/poetry/core/spdx/data/licenses.json index b598305bb..6a241f66a 100644 --- a/conda_lock/_vendor/poetry/core/spdx/data/licenses.json +++ b/conda_lock/_vendor/poetry/core/spdx/data/licenses.json @@ -1040,17 +1040,17 @@ false ], "LiLiQ-P-1.1": [ - "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1", + "Licence Libre du Québec – Permissive version 1.1", true, false ], "LiLiQ-R-1.1": [ - "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1", + "Licence Libre du Québec – Réciprocité version 1.1", true, false ], "LiLiQ-Rplus-1.1": [ - "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1", + "Licence Libre du Québec – Réciprocité forte version 1.1", true, false ], diff --git a/conda_lock/_vendor/poetry/core/spdx/helpers.py b/conda_lock/_vendor/poetry/core/spdx/helpers.py new file mode 100644 index 000000000..4ffbf281c --- /dev/null +++ b/conda_lock/_vendor/poetry/core/spdx/helpers.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import functools +import json +import os + +from conda_lock._vendor.poetry.core.spdx.license import License + + +def license_by_id(identifier: str) -> License: + if not identifier: + raise ValueError("A license identifier is required") + + licenses = _load_licenses() + return licenses.get( + identifier.lower(), License(identifier, identifier, False, False) + ) + + +@functools.lru_cache() +def _load_licenses() -> dict[str, License]: + licenses = {} + licenses_file = os.path.join(os.path.dirname(__file__), "data", "licenses.json") + + with open(licenses_file, encoding="utf-8") as f: + data = json.loads(f.read()) + + for name, license_info in data.items(): + license = License(name, license_info[0], license_info[1], license_info[2]) + licenses[name.lower()] = license + + full_name = license_info[0].lower() + if full_name in licenses: + existing_license = licenses[full_name] + if not existing_license.is_deprecated: + continue + + licenses[full_name] = license + + # Add a Proprietary license for non-standard licenses + licenses["proprietary"] = License("Proprietary", "Proprietary", False, False) + + return licenses + + +if __name__ == "__main__": + from conda_lock._vendor.poetry.core.spdx.updater import Updater + + updater = Updater() + updater.dump() diff --git a/conda_lock/_vendor/poetry/core/spdx/license.py b/conda_lock/_vendor/poetry/core/spdx/license.py index f5a9fb6d6..901a1cbcc 100644 --- a/conda_lock/_vendor/poetry/core/spdx/license.py +++ b/conda_lock/_vendor/poetry/core/spdx/license.py @@ -1,8 +1,13 @@ +from __future__ import annotations + from collections import namedtuple -from typing import Optional class License(namedtuple("License", "id name is_osi_approved is_deprecated")): + id: str + name: str + is_osi_approved: bool + is_deprecated: bool CLASSIFIER_SUPPORTED = { # Not OSI Approved @@ -131,7 +136,7 @@ class License(namedtuple("License", "id name is_osi_approved is_deprecated")): } @property - def classifier(self): # type: () -> str + def classifier(self) -> str: parts = ["License"] if self.is_osi_approved: @@ -144,7 +149,7 @@ def classifier(self): # type: () -> str return " :: ".join(parts) @property - def classifier_name(self): # type: () -> Optional[str] + def classifier_name(self) -> str | None: if self.id not in self.CLASSIFIER_SUPPORTED: if self.is_osi_approved: return None diff --git a/conda_lock/_vendor/poetry/core/spdx/updater.py b/conda_lock/_vendor/poetry/core/spdx/updater.py index 30c3a5190..9f6ff37d2 100644 --- a/conda_lock/_vendor/poetry/core/spdx/updater.py +++ b/conda_lock/_vendor/poetry/core/spdx/updater.py @@ -1,26 +1,19 @@ +from __future__ import annotations + import json import os -from io import open from typing import Any -from typing import Dict -from typing import Optional - - -try: - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen +from urllib.request import urlopen class Updater: - BASE_URL = "https://raw.githubusercontent.com/spdx/license-list-data/master/json/" - def __init__(self, base_url=BASE_URL): # type: (str) -> None + def __init__(self, base_url: str = BASE_URL) -> None: self._base_url = base_url - def dump(self, file=None): # type: (Optional[str]) -> None + def dump(self, file: str | None = None) -> None: if file is None: file = os.path.join(os.path.dirname(__file__), "data", "licenses.json") @@ -31,7 +24,7 @@ def dump(self, file=None): # type: (Optional[str]) -> None json.dumps(self.get_licenses(licenses_url), indent=2, sort_keys=True) ) - def get_licenses(self, url): # type: (str) -> Dict[str, Any] + def get_licenses(self, url: str) -> dict[str, Any]: licenses = {} with urlopen(url) as r: data = json.loads(r.read().decode()) diff --git a/conda_lock/_vendor/poetry/core/toml/__init__.py b/conda_lock/_vendor/poetry/core/toml/__init__.py index bda2d2453..e2d391ecd 100644 --- a/conda_lock/_vendor/poetry/core/toml/__init__.py +++ b/conda_lock/_vendor/poetry/core/toml/__init__.py @@ -1,5 +1,7 @@ +from __future__ import annotations + from conda_lock._vendor.poetry.core.toml.exceptions import TOMLError from conda_lock._vendor.poetry.core.toml.file import TOMLFile -__all__ = [clazz.__name__ for clazz in {TOMLError, TOMLFile}] +__all__ = ["TOMLError", "TOMLFile"] diff --git a/conda_lock/_vendor/poetry/core/toml/exceptions.py b/conda_lock/_vendor/poetry/core/toml/exceptions.py index 4352f48dc..3783abfc3 100644 --- a/conda_lock/_vendor/poetry/core/toml/exceptions.py +++ b/conda_lock/_vendor/poetry/core/toml/exceptions.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from tomlkit.exceptions import TOMLKitError from conda_lock._vendor.poetry.core.exceptions import PoetryCoreException -class TOMLError(TOMLKitError, PoetryCoreException): +class TOMLError(TOMLKitError, PoetryCoreException): # type: ignore[misc] pass diff --git a/conda_lock/_vendor/poetry/core/toml/file.py b/conda_lock/_vendor/poetry/core/toml/file.py index c5de7a9a2..532d0e553 100644 --- a/conda_lock/_vendor/poetry/core/toml/file.py +++ b/conda_lock/_vendor/poetry/core/toml/file.py @@ -1,40 +1,42 @@ +from __future__ import annotations + +from pathlib import Path from typing import TYPE_CHECKING from typing import Any -from typing import Union -from tomlkit.exceptions import TOMLKitError from tomlkit.toml_file import TOMLFile as BaseTOMLFile -from conda_lock._vendor.poetry.core.toml import TOMLError -from conda_lock._vendor.poetry.core.utils._compat import Path - if TYPE_CHECKING: - from tomlkit.toml_document import TOMLDocument # noqa + from tomlkit.toml_document import TOMLDocument -class TOMLFile(BaseTOMLFile): - def __init__(self, path): # type: (Union[str, Path]) -> None +class TOMLFile(BaseTOMLFile): # type: ignore[misc] + def __init__(self, path: str | Path) -> None: if isinstance(path, str): path = Path(path) - super(TOMLFile, self).__init__(path.as_posix()) + super().__init__(path.as_posix()) self.__path = path @property - def path(self): # type: () -> Path + def path(self) -> Path: return self.__path - def exists(self): # type: () -> bool + def exists(self) -> bool: return self.__path.exists() - def read(self): # type: () -> "TOMLDocument" + def read(self) -> TOMLDocument: + from tomlkit.exceptions import TOMLKitError + + from conda_lock._vendor.poetry.core.toml import TOMLError + try: - return super(TOMLFile, self).read() + return super().read() except (ValueError, TOMLKitError) as e: - raise TOMLError("Invalid TOML file {}: {}".format(self.path.as_posix(), e)) + raise TOMLError(f"Invalid TOML file {self.path.as_posix()}: {e}") - def __getattr__(self, item): # type: (str) -> Any + def __getattr__(self, item: str) -> Any: return getattr(self.__path, item) - def __str__(self): # type: () -> str + def __str__(self) -> str: return self.__path.as_posix() diff --git a/conda_lock/_vendor/poetry/core/utils/_compat.py b/conda_lock/_vendor/poetry/core/utils/_compat.py index 7c5daa9f6..7b3f59e73 100644 --- a/conda_lock/_vendor/poetry/core/utils/_compat.py +++ b/conda_lock/_vendor/poetry/core/utils/_compat.py @@ -1,125 +1,6 @@ -import sys - -from typing import AnyStr -from typing import List -from typing import Optional -from typing import Union - -import six.moves.urllib.parse as urllib_parse - - -urlparse = urllib_parse - - -try: # Python 2 - long = long - unicode = unicode - basestring = basestring -except NameError: # Python 3 - long = int - unicode = str - basestring = str +from __future__ import annotations +import sys -PY2 = sys.version_info[0] == 2 -PY34 = sys.version_info >= (3, 4) -PY35 = sys.version_info >= (3, 5) -PY36 = sys.version_info >= (3, 6) -PY37 = sys.version_info >= (3, 7) WINDOWS = sys.platform == "win32" - -if PY2: - import pipes - - shell_quote = pipes.quote -else: - import shlex - - shell_quote = shlex.quote - -if PY35: - from pathlib import Path # noqa -else: - from pathlib2 import Path # noqa - -if not PY36: - from collections import OrderedDict # noqa -else: - OrderedDict = dict - - -try: - FileNotFoundError -except NameError: - FileNotFoundError = IOError # noqa - - -def decode( - string, encodings=None -): # type: (Union[AnyStr, unicode], Optional[str]) -> Union[str, bytes] - if not PY2 and not isinstance(string, bytes): - return string - - if PY2 and isinstance(string, unicode): - return string - - encodings = encodings or ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - try: - return string.decode(encoding) - except (UnicodeEncodeError, UnicodeDecodeError): - pass - - return string.decode(encodings[0], errors="ignore") - - -def encode( - string, encodings=None -): # type: (AnyStr, Optional[str]) -> Union[str, bytes] - if not PY2 and isinstance(string, bytes): - return string - - if PY2 and isinstance(string, str): - return string - - encodings = encodings or ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - try: - return string.encode(encoding) - except (UnicodeEncodeError, UnicodeDecodeError): - pass - - return string.encode(encodings[0], errors="ignore") - - -def to_str(string): # type: (AnyStr) -> str - if isinstance(string, str) or not isinstance(string, (unicode, bytes)): - return string - - if PY2: - method = "encode" - else: - method = "decode" - - encodings = ["utf-8", "latin1", "ascii"] - - for encoding in encodings: - try: - return getattr(string, method)(encoding) - except (UnicodeEncodeError, UnicodeDecodeError): - pass - - return getattr(string, method)(encodings[0], errors="ignore") - - -def list_to_shell_command(cmd): # type: (List[str]) -> str - executable = cmd[0] - - if " " in executable: - executable = '"{}"'.format(executable) - cmd[0] = executable - - return " ".join(cmd) diff --git a/conda_lock/_vendor/poetry/core/utils/helpers.py b/conda_lock/_vendor/poetry/core/utils/helpers.py index e17b36d47..bc007f45a 100644 --- a/conda_lock/_vendor/poetry/core/utils/helpers.py +++ b/conda_lock/_vendor/poetry/core/utils/helpers.py @@ -1,48 +1,47 @@ +from __future__ import annotations + import os -import re import shutil import stat import tempfile +import unicodedata +import warnings from contextlib import contextmanager +from pathlib import Path from typing import Any from typing import Iterator -from typing import List -from typing import Union - -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.version import Version - - -try: - from collections.abc import Mapping -except ImportError: - from collections import Mapping +from packaging.utils import canonicalize_name -_canonicalize_regex = re.compile(r"[-_]+") +from conda_lock._vendor.poetry.core.version.pep440 import PEP440Version -def canonicalize_name(name): # type: (str) -> str - return _canonicalize_regex.sub("-", name).lower() +def combine_unicode(string: str) -> str: + return unicodedata.normalize("NFC", string) -def module_name(name): # type: (str) -> str - return canonicalize_name(name).replace(".", "_").replace("-", "_") +def module_name(name: str) -> str: + return canonicalize_name(name).replace("-", "_") -def normalize_version(version): # type: (str) -> str - return str(Version(version)) +def normalize_version(version: str) -> str: + warnings.warn( + "normalize_version() is deprecated. Use Version.parse().to_string() instead.", + DeprecationWarning, + stacklevel=2, + ) + return PEP440Version.parse(version).to_string() @contextmanager -def temporary_directory(*args, **kwargs): # type: (*Any, **Any) -> Iterator[str] +def temporary_directory(*args: Any, **kwargs: Any) -> Iterator[str]: name = tempfile.mkdtemp(*args, **kwargs) yield name safe_rmtree(name) -def parse_requires(requires): # type: (str) -> List[str] +def parse_requires(requires: str) -> list[str]: lines = requires.split("\n") requires_dist = [] @@ -60,15 +59,15 @@ def parse_requires(requires): # type: (str) -> List[str] # extras or conditional dependencies marker = line.lstrip("[").rstrip("]") if ":" not in marker: - extra, marker = marker, None + extra, marker = marker, "" else: extra, marker = marker.split(":") if extra: if marker: - marker = '{} and extra == "{}"'.format(marker, extra) + marker = f'{marker} and extra == "{extra}"' else: - marker = 'extra == "{}"'.format(extra) + marker = f'extra == "{extra}"' if marker: current_marker = marker @@ -76,14 +75,14 @@ def parse_requires(requires): # type: (str) -> List[str] continue if current_marker: - line = "{} ; {}".format(line, current_marker) + line = f"{line} ; {current_marker}" requires_dist.append(line) return requires_dist -def _on_rm_error(func, path, exc_info): # type: (Any, Union[str, Path], Any) -> None +def _on_rm_error(func: Any, path: str | Path, exc_info: Any) -> None: if not os.path.exists(path): return @@ -91,16 +90,18 @@ def _on_rm_error(func, path, exc_info): # type: (Any, Union[str, Path], Any) -> func(path) -def safe_rmtree(path): # type: (Union[str, Path]) -> None +def safe_rmtree(path: str | Path) -> None: if Path(path).is_symlink(): return os.unlink(str(path)) shutil.rmtree(path, onerror=_on_rm_error) -def merge_dicts(d1, d2): # type: (dict, dict) -> None - for k, v in d2.items(): - if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping): - merge_dicts(d1[k], d2[k]) - else: - d1[k] = d2[k] +def readme_content_type(path: str | Path) -> str: + suffix = Path(path).suffix + if suffix == ".rst": + return "text/x-rst" + elif suffix in [".md", ".markdown"]: + return "text/markdown" + else: + return "text/plain" diff --git a/conda_lock/_vendor/poetry/core/utils/patterns.py b/conda_lock/_vendor/poetry/core/utils/patterns.py index 1d6413c26..c2d9d9bfb 100644 --- a/conda_lock/_vendor/poetry/core/utils/patterns.py +++ b/conda_lock/_vendor/poetry/core/utils/patterns.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re diff --git a/conda_lock/_vendor/poetry/core/utils/toml_file.py b/conda_lock/_vendor/poetry/core/utils/toml_file.py index bd59105af..03c0bd319 100644 --- a/conda_lock/_vendor/poetry/core/utils/toml_file.py +++ b/conda_lock/_vendor/poetry/core/utils/toml_file.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + from typing import Any from conda_lock._vendor.poetry.core.toml import TOMLFile @@ -6,14 +7,14 @@ class TomlFile(TOMLFile): @classmethod - def __new__(cls, *args, **kwargs): # type: (*Any, **Any) -> TOMLFile + def __new__(cls: type[TOMLFile], *args: Any, **kwargs: Any) -> TomlFile: import warnings + this_import = f"{cls.__module__}.{cls.__name__}" + new_import = f"{TOMLFile.__module__}.{TOMLFile.__name__}" warnings.warn( - "Use of {}.{} has been deprecated, use {}.{} instead.".format( - cls.__module__, cls.__name__, TOMLFile.__module__, TOMLFile.__name__, - ), + f"Use of {this_import} has been deprecated, use {new_import} instead.", category=DeprecationWarning, stacklevel=2, ) - return super(TomlFile, cls).__new__(cls) + return super().__new__(cls) # type: ignore[no-any-return,misc] diff --git a/conda_lock/_vendor/poetry/core/vcs/__init__.py b/conda_lock/_vendor/poetry/core/vcs/__init__.py index a84648737..37aab6a56 100644 --- a/conda_lock/_vendor/poetry/core/vcs/__init__.py +++ b/conda_lock/_vendor/poetry/core/vcs/__init__.py @@ -1,24 +1,29 @@ +from __future__ import annotations + import os import subprocess -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils._compat import decode +from pathlib import Path -from .git import Git +from conda_lock._vendor.poetry.core.vcs.git import Git -def get_vcs(directory): # type: (Path) -> Git +def get_vcs(directory: Path) -> Git | None: working_dir = Path.cwd() os.chdir(str(directory.resolve())) + vcs: Git | None + try: - from .git import executable + from conda_lock._vendor.poetry.core.vcs.git import executable - git_dir = decode( + git_dir = ( subprocess.check_output( [executable(), "rev-parse", "--show-toplevel"], stderr=subprocess.STDOUT ) - ).strip() + .decode() + .strip() + ) vcs = Git(Path(git_dir)) diff --git a/conda_lock/_vendor/poetry/core/vcs/git.py b/conda_lock/_vendor/poetry/core/vcs/git.py index 529f872d6..3e21e9456 100644 --- a/conda_lock/_vendor/poetry/core/vcs/git.py +++ b/conda_lock/_vendor/poetry/core/vcs/git.py @@ -1,115 +1,108 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import re import subprocess from collections import namedtuple +from pathlib import Path from typing import Any -from typing import Optional -from conda_lock._vendor.poetry.core.utils._compat import PY36 from conda_lock._vendor.poetry.core.utils._compat import WINDOWS -from conda_lock._vendor.poetry.core.utils._compat import Path -from conda_lock._vendor.poetry.core.utils._compat import decode -pattern_formats = { - "protocol": r"\w+", - "user": r"[a-zA-Z0-9_.-]+", - "resource": r"[a-zA-Z0-9_.-]+", - "port": r"\d+", - "path": r"[\w~.\-/\\]+", - "name": r"[\w~.\-]+", - "rev": r"[^@#]+", -} +PROTOCOL = r"\w+" +USER = r"[a-zA-Z0-9_.-]+" +RESOURCE = r"[a-zA-Z0-9_.-]+" +PORT = r"\d+" +PATH = r"[\w~.\-/\\\$]+" +NAME = r"[\w~.\-]+" +REV = r"[^@#]+?" +SUBDIR = r"[\w\-/\\]+" PATTERNS = [ re.compile( r"^(git\+)?" r"(?Phttps?|git|ssh|rsync|file)://" - r"(?:(?P{user})@)?" - r"(?P{resource})?" - r"(:(?P{port}))?" - r"(?P[:/\\]({path}[/\\])?" - r"((?P{name}?)(\.git|[/\\])?)?)" - r"([@#](?P{rev}))?" - r"$".format( - user=pattern_formats["user"], - resource=pattern_formats["resource"], - port=pattern_formats["port"], - path=pattern_formats["path"], - name=pattern_formats["name"], - rev=pattern_formats["rev"], - ) + rf"(?:(?P{USER})@)?" + rf"(?P{RESOURCE})?" + rf"(:(?P{PORT}))?" + rf"(?P[:/\\]({PATH}[/\\])?" + rf"((?P{NAME}?)(\.git|[/\\])?)?)" + r"(?:" + r"#egg=?.+" + r"|" + rf"#(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})" + r"|" + rf"[@#](?P{REV})(?:[&#](?:egg=.+?|(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})))?" + r")?" + r"$" ), re.compile( r"(git\+)?" - r"((?P{protocol})://)" - r"(?:(?P{user})@)?" - r"(?P{resource}:?)" - r"(:(?P{port}))?" - r"(?P({path})" - r"(?P{name})(\.git|/)?)" - r"([@#](?P{rev}))?" - r"$".format( - protocol=pattern_formats["protocol"], - user=pattern_formats["user"], - resource=pattern_formats["resource"], - port=pattern_formats["port"], - path=pattern_formats["path"], - name=pattern_formats["name"], - rev=pattern_formats["rev"], - ) + rf"((?P{PROTOCOL})://)" + rf"(?:(?P{USER})@)?" + rf"(?P{RESOURCE}:?)" + rf"(:(?P{PORT}))?" + rf"(?P({PATH})" + rf"(?P{NAME})(\.git|/)?)" + r"(?:" + r"#egg=?.+" + r"|" + rf"#(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})" + r"|" + rf"[@#](?P{REV})(?:[&#](?:egg=.+?|(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})))?" + r")?" + r"$" ), re.compile( - r"^(?:(?P{user})@)?" - r"(?P{resource})" - r"(:(?P{port}))?" - r"(?P([:/]{path}/)" - r"(?P{name})(\.git|/)?)" - r"([@#](?P{rev}))?" - r"$".format( - user=pattern_formats["user"], - resource=pattern_formats["resource"], - port=pattern_formats["port"], - path=pattern_formats["path"], - name=pattern_formats["name"], - rev=pattern_formats["rev"], - ) + rf"^(?:(?P{USER})@)?" + rf"(?P{RESOURCE})" + rf"(:(?P{PORT}))?" + rf"(?P([:/]{PATH}/)" + rf"(?P{NAME})(\.git|/)?)" + r"(?:" + r"#egg=.+?" + r"|" + rf"#(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})" + r"|" + rf"[@#](?P{REV})(?:[&#](?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR}))?" + r")?" + r"$" ), re.compile( - r"((?P{user})@)?" - r"(?P{resource})" + rf"((?P{USER})@)?" + rf"(?P{RESOURCE})" r"[:/]{{1,2}}" - r"(?P({path})" - r"(?P{name})(\.git|/)?)" - r"([@#](?P{rev}))?" - r"$".format( - user=pattern_formats["user"], - resource=pattern_formats["resource"], - path=pattern_formats["path"], - name=pattern_formats["name"], - rev=pattern_formats["rev"], - ) + rf"(?P({PATH})" + rf"(?P{NAME})(\.git|/)?)" + r"(?:" + r"#egg=?.+" + r"|" + rf"#(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})" + r"|" + rf"[@#](?P{REV})(?:[&#](?:egg=.+?|(?:egg=.+?&subdirectory=|subdirectory=)(?P{SUBDIR})))?" + r")?" + r"$" ), ] class GitError(RuntimeError): - pass class ParsedUrl: def __init__( self, - protocol, # type: Optional[str] - resource, # type: Optional[str] - pathname, # type: Optional[str] - user, # type: Optional[str] - port, # type: Optional[str] - name, # type: Optional[str] - rev, # type: Optional[str] - ): + protocol: str | None, + resource: str | None, + pathname: str | None, + user: str | None, + port: str | None, + name: str | None, + rev: str | None, + subdirectory: str | None = None, + ) -> None: self.protocol = protocol self.resource = resource self.pathname = pathname @@ -117,9 +110,10 @@ def __init__( self.port = port self.name = name self.rev = rev + self.subdirectory = subdirectory @classmethod - def parse(cls, url): # type: (str) -> ParsedUrl + def parse(cls, url: str) -> ParsedUrl: for pattern in PATTERNS: m = pattern.match(url) if m: @@ -132,54 +126,53 @@ def parse(cls, url): # type: (str) -> ParsedUrl groups.get("port"), groups.get("name"), groups.get("rev"), + groups.get("rev_subdirectory") or groups.get("subdirectory"), ) - raise ValueError('Invalid git url "{}"'.format(url)) + raise ValueError(f'Invalid git url "{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): # type: () -> str + def url(self) -> str: + protocol = f"{self.protocol}://" if self.protocol else "" + user = f"{self.user}@" if self.user else "" + port = f":{self.port}" if self.port else "" + path = "/" + (self.pathname or "").lstrip(":/") + return f"{protocol}{user}{self.resource}{port}{path}" + + def format(self) -> str: return self.url - def __str__(self): # type: () -> str + def __str__(self) -> str: return self.format() -GitUrl = namedtuple("GitUrl", ["url", "revision"]) +GitUrl = namedtuple("GitUrl", ["url", "revision", "subdirectory"]) -_executable = None +_executable: str | None = None -def executable(): +def executable() -> str: global _executable if _executable is not None: return _executable - if WINDOWS and PY36: + if WINDOWS: # Finding git via where.exe where = "%WINDIR%\\System32\\where.exe" - paths = decode( - subprocess.check_output([where, "git"], shell=True, encoding="oem") + paths = subprocess.check_output( + [where, "git"], shell=True, encoding="oem" ).split("\n") for path in paths: if not path: continue - path = Path(path.strip()) + _path = Path(path.strip()) try: - path.relative_to(Path.cwd()) + _path.relative_to(Path.cwd()) except ValueError: - _executable = str(path) + _executable = str(_path) break else: @@ -191,22 +184,20 @@ def executable(): return _executable -def _reset_executable(): +def _reset_executable() -> None: global _executable _executable = None class GitConfig: - def __init__(self, requires_git_presence=False): # type: (bool) -> None + def __init__(self, requires_git_presence: bool = False) -> None: self._config = {} try: - config_list = decode( - subprocess.check_output( - [executable(), "config", "-l"], stderr=subprocess.STDOUT - ) - ) + config_list = subprocess.check_output( + [executable(), "config", "-l"], stderr=subprocess.STDOUT + ).decode() m = re.findall("(?ms)^([^=]+)=(.*?)$", config_list) if m: @@ -216,31 +207,36 @@ def __init__(self, requires_git_presence=False): # type: (bool) -> None if requires_git_presence: raise - def get(self, key, default=None): # type: (Any, Optional[Any]) -> Any + def get(self, key: Any, default: Any | None = None) -> Any: return self._config.get(key, default) - def __getitem__(self, item): # type: (Any) -> Any + def __getitem__(self, item: Any) -> Any: return self._config[item] class Git: - def __init__(self, work_dir=None): # type: (Optional[Path]) -> None + def __init__(self, work_dir: Path | None = None) -> None: self._config = GitConfig(requires_git_presence=True) self._work_dir = work_dir @classmethod - def normalize_url(cls, url): # type: (str) -> GitUrl + def normalize_url(cls, url: str) -> GitUrl: parsed = ParsedUrl.parse(url) formatted = re.sub(r"^git\+", "", url) if parsed.rev: - formatted = re.sub(r"[#@]{}$".format(parsed.rev), "", formatted) + formatted = re.sub(rf"[#@]{parsed.rev}(?=[#&]?)(?!\=)", "", formatted) + + if parsed.subdirectory: + formatted = re.sub( + rf"[#&]subdirectory={parsed.subdirectory}$", "", formatted + ) altered = parsed.format() != formatted if altered: if re.match(r"^git\+https?", url) and re.match( - r"^/?:[^0-9]", parsed.pathname + r"^/?:[^0-9]", parsed.pathname or "" ): normalized = re.sub(r"git\+(.*:[^:]+):(.*)", "\\1/\\2", url) elif re.match(r"^git\+file", url): @@ -250,18 +246,38 @@ def normalize_url(cls, url): # type: (str) -> GitUrl else: normalized = parsed.format() - return GitUrl(re.sub(r"#[^#]*$", "", normalized), parsed.rev) + return GitUrl( + re.sub(r"#[^#]*$", "", normalized), parsed.rev, parsed.subdirectory + ) @property - def config(self): # type: () -> GitConfig + def config(self) -> GitConfig: return self._config - def clone(self, repository, dest): # type: (str, Path) -> str + @property + def version(self) -> tuple[int, int, int]: + output = self.run("version") + version = re.search(r"(\d+)\.(\d+)\.(\d+)", output) + if not version: + return (0, 0, 0) + return int(version.group(1)), int(version.group(2)), int(version.group(3)) + + def clone(self, repository: str, dest: Path) -> str: self._check_parameter(repository) - - return self.run("clone", "--recurse-submodules", "--", repository, str(dest)) - - def checkout(self, rev, folder=None): # type: (str, Optional[Path]) -> str + cmd = [ + "clone", + "--filter=blob:none", + "--recurse-submodules", + "--", + repository, + str(dest), + ] + # Blobless clones introduced in Git 2.17 + if self.version < (2, 17): + cmd.remove("--filter=blob:none") + return self.run(*cmd) + + def checkout(self, rev: str, folder: Path | None = None) -> str: args = [] if folder is None and self._work_dir: folder = self._work_dir @@ -276,23 +292,15 @@ def checkout(self, rev, folder=None): # type: (str, Optional[Path]) -> str self._check_parameter(rev) - args += ["checkout", rev] + args += ["checkout", "--recurse-submodules", rev] return self.run(*args) - def rev_parse(self, rev, folder=None): # type: (str, Optional[Path]) -> str + def rev_parse(self, rev: str, folder: Path | None = None) -> str: args = [] if folder is None and self._work_dir: folder = self._work_dir - if folder: - args += [ - "--git-dir", - (folder / ".git").as_posix(), - "--work-tree", - folder.as_posix(), - ] - self._check_parameter(rev) # We need "^0" (an alternative to "^{commit}") to ensure that the @@ -305,9 +313,17 @@ def rev_parse(self, rev, folder=None): # type: (str, Optional[Path]) -> str # they should not be escaped. args += ["rev-parse", rev + "^0"] - return self.run(*args) + return self.run(*args, folder=folder) + + def get_current_branch(self, folder: Path | None = None) -> str: + if folder is None and self._work_dir: + folder = self._work_dir - def get_ignored_files(self, folder=None): # type: (Optional[Path]) -> list + output = self.run("symbolic-ref", "--short", "HEAD", folder=folder) + + return output.strip() + + def get_ignored_files(self, folder: Path | None = None) -> list[str]: args = [] if folder is None and self._work_dir: folder = self._work_dir @@ -325,7 +341,7 @@ def get_ignored_files(self, folder=None): # type: (Optional[Path]) -> list return output.strip().split("\n") - def remote_urls(self, folder=None): # type: (Optional[Path]) -> dict + def remote_urls(self, folder: Path | None = None) -> dict[str, str]: output = self.run( "config", "--get-regexp", r"remote\..*\.url", folder=folder ).strip() @@ -337,12 +353,12 @@ def remote_urls(self, folder=None): # type: (Optional[Path]) -> dict return urls - def remote_url(self, folder=None): # type: (Optional[Path]) -> str + def remote_url(self, folder: Path | None = None) -> str: urls = self.remote_urls(folder=folder) return urls.get("remote.origin.url", urls[list(urls.keys())[0]]) - def run(self, *args, **kwargs): # type: (*Any, **Any) -> str + def run(self, *args: Any, **kwargs: Any) -> str: folder = kwargs.pop("folder", None) if folder: args = ( @@ -352,15 +368,17 @@ def run(self, *args, **kwargs): # type: (*Any, **Any) -> str folder.as_posix(), ) + args - return decode( + return ( subprocess.check_output( [executable()] + list(args), stderr=subprocess.STDOUT ) - ).strip() + .decode() + .strip() + ) - def _check_parameter(self, parameter): # type: (str) -> None + def _check_parameter(self, parameter: str) -> None: """ Checks a git parameter to avoid unwanted code execution. """ if parameter.strip().startswith("-"): - raise GitError("Invalid Git parameter: {}".format(parameter)) + raise GitError(f"Invalid Git parameter: {parameter}") diff --git a/conda_lock/_vendor/poetry/core/version/__init__.py b/conda_lock/_vendor/poetry/core/version/__init__.py index 62d0349fe..e69de29bb 100644 --- a/conda_lock/_vendor/poetry/core/version/__init__.py +++ b/conda_lock/_vendor/poetry/core/version/__init__.py @@ -1,45 +0,0 @@ -import operator - -from typing import Union - -from .exceptions import InvalidVersion -from .legacy_version import LegacyVersion -from .version import Version - - -OP_EQ = operator.eq -OP_LT = operator.lt -OP_LE = operator.le -OP_GT = operator.gt -OP_GE = operator.ge -OP_NE = operator.ne - -_trans_op = { - "=": OP_EQ, - "==": OP_EQ, - "<": OP_LT, - "<=": OP_LE, - ">": OP_GT, - ">=": OP_GE, - "!=": OP_NE, -} - - -def parse( - version, # type: str - strict=False, # type: bool -): # type:(...) -> Union[Version, LegacyVersion] - """ - Parse the given version string and return either a :class:`Version` object - or a LegacyVersion object depending on if the given version is - a valid PEP 440 version or a legacy version. - - If strict=True only PEP 440 versions will be accepted. - """ - try: - return Version(version) - except InvalidVersion: - if strict: - raise - - return LegacyVersion(version) diff --git a/conda_lock/_vendor/poetry/core/version/base.py b/conda_lock/_vendor/poetry/core/version/base.py deleted file mode 100644 index 826f86226..000000000 --- a/conda_lock/_vendor/poetry/core/version/base.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Callable - - -class BaseVersion: - def __init__(self, version): # type: (str) -> None - self._version = str(version) - self._key = None - - def __hash__(self): # type: () -> int - return hash(self._key) - - def __lt__(self, other): # type: (BaseVersion) -> bool - return self._compare(other, lambda s, o: s < o) - - def __le__(self, other): # type: (BaseVersion) -> bool - return self._compare(other, lambda s, o: s <= o) - - def __eq__(self, other): # type: (BaseVersion) -> bool - return self._compare(other, lambda s, o: s == o) - - def __ge__(self, other): # type: (BaseVersion) -> bool - return self._compare(other, lambda s, o: s >= o) - - def __gt__(self, other): # type: (BaseVersion) -> bool - return self._compare(other, lambda s, o: s > o) - - def __ne__(self, other): # type: (BaseVersion) -> bool - return self._compare(other, lambda s, o: s != o) - - def _compare(self, other, method): # type: (BaseVersion, Callable) -> bool - if not isinstance(other, BaseVersion): - return NotImplemented - - return method(self._key, other._key) diff --git a/conda_lock/_vendor/poetry/core/version/exceptions.py b/conda_lock/_vendor/poetry/core/version/exceptions.py index 741b13ca1..752fada64 100644 --- a/conda_lock/_vendor/poetry/core/version/exceptions.py +++ b/conda_lock/_vendor/poetry/core/version/exceptions.py @@ -1,3 +1,5 @@ -class InvalidVersion(ValueError): +from __future__ import annotations + +class InvalidVersion(ValueError): pass diff --git a/conda_lock/_vendor/poetry/core/version/grammars/__init__.py b/conda_lock/_vendor/poetry/core/version/grammars/__init__.py index e69de29bb..caf504b46 100644 --- a/conda_lock/_vendor/poetry/core/version/grammars/__init__.py +++ b/conda_lock/_vendor/poetry/core/version/grammars/__init__.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from pathlib import Path + + +GRAMMAR_DIR = Path(__file__).parent + +GRAMMAR_PEP_508_CONSTRAINTS = GRAMMAR_DIR / "pep508.lark" + +GRAMMAR_PEP_508_MARKERS = GRAMMAR_DIR / "markers.lark" diff --git a/conda_lock/_vendor/poetry/core/version/grammars/markers.lark b/conda_lock/_vendor/poetry/core/version/grammars/markers.lark index 189ab02a5..e0079c2a2 100644 --- a/conda_lock/_vendor/poetry/core/version/grammars/markers.lark +++ b/conda_lock/_vendor/poetry/core/version/grammars/markers.lark @@ -15,7 +15,6 @@ MARKER_NAME: "implementation_version" | "platform_system" | "python_version" | "sys_platform" - | "sys_platform" | "os_name" | "os.name" | "sys.platform" diff --git a/conda_lock/_vendor/poetry/core/version/helpers.py b/conda_lock/_vendor/poetry/core/version/helpers.py index ff4805446..367693032 100644 --- a/conda_lock/_vendor/poetry/core/version/helpers.py +++ b/conda_lock/_vendor/poetry/core/version/helpers.py @@ -1,13 +1,14 @@ +from __future__ import annotations + from typing import TYPE_CHECKING -from typing import Union -from conda_lock._vendor.poetry.core.semver import Version -from conda_lock._vendor.poetry.core.semver import VersionUnion -from conda_lock._vendor.poetry.core.semver import parse_constraint +from conda_lock._vendor.poetry.core.constraints.version import Version +from conda_lock._vendor.poetry.core.constraints.version import VersionUnion +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.semver import VersionConstraint # noqa + from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint PYTHON_VERSION = [ "2.7.*", @@ -21,28 +22,26 @@ "3.7.*", "3.8.*", "3.9.*", + "3.10.*", + "3.11.*", ] -def format_python_constraint( - constraint, -): # type: (Union[Version, VersionUnion, "VersionConstraint"]) -> str +def format_python_constraint(constraint: VersionConstraint) -> str: """ This helper will help in transforming disjunctive constraint into proper constraint. """ if isinstance(constraint, Version): if constraint.precision >= 3: - return "=={}".format(str(constraint)) + return f"=={str(constraint)}" # Transform 3.6 or 3 if constraint.precision == 2: # 3.6 - constraint = parse_constraint( - "~{}.{}".format(constraint.major, constraint.minor) - ) + constraint = parse_constraint(f"~{constraint.major}.{constraint.minor}") else: - constraint = parse_constraint("^{}.0".format(constraint.major)) + constraint = parse_constraint(f"^{constraint.major}.0") if not isinstance(constraint, VersionUnion): return str(constraint) diff --git a/conda_lock/_vendor/poetry/core/version/legacy_version.py b/conda_lock/_vendor/poetry/core/version/legacy_version.py deleted file mode 100644 index adaa53d7e..000000000 --- a/conda_lock/_vendor/poetry/core/version/legacy_version.py +++ /dev/null @@ -1,92 +0,0 @@ -import re - -from typing import Tuple - -from .base import BaseVersion - - -class LegacyVersion(BaseVersion): - def __init__(self, version): # type: (str) -> None - self._version = str(version) - self._key = _legacy_cmpkey(self._version) - - def __str__(self): # type: () -> str - return self._version - - def __repr__(self): # type: () -> str - return "".format(repr(str(self))) - - @property - def public(self): # type: () -> str - return self._version - - @property - def base_version(self): # type: () -> str - return self._version - - @property - def local(self): # type: () -> None - return None - - @property - def is_prerelease(self): # type: () -> bool - return False - - @property - def is_postrelease(self): # type: () -> bool - return False - - -_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) - -_legacy_version_replacement_map = { - "pre": "c", - "preview": "c", - "-": "final-", - "rc": "c", - "dev": "@", -} - - -def _parse_version_parts(s): # type: (str) -> str - for part in _legacy_version_component_re.split(s): - part = _legacy_version_replacement_map.get(part, part) - - if not part or part == ".": - continue - - if part[:1] in "0123456789": - # pad for numeric comparison - yield part.zfill(8) - else: - yield "*" + part - - # ensure that alpha/beta/candidate are before final - yield "*final" - - -def _legacy_cmpkey(version): # type: (str) -> Tuple[int, Tuple[str]] - # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch - # greater than or equal to 0. This will effectively put the LegacyVersion, - # which uses the defacto standard originally implemented by setuptools, - # as before all PEP 440 versions. - epoch = -1 - - # This scheme is taken from pkg_resources.parse_version setuptools prior to - # it's adoption of the packaging library. - parts = [] - for part in _parse_version_parts(version.lower()): - if part.startswith("*"): - # remove "-" before a prerelease tag - if part < "*final": - while parts and parts[-1] == "*final-": - parts.pop() - - # remove trailing zeros from each series of numeric parts - while parts and parts[-1] == "00000000": - parts.pop() - - parts.append(part) - parts = tuple(parts) - - return epoch, parts diff --git a/conda_lock/_vendor/poetry/core/version/markers.py b/conda_lock/_vendor/poetry/core/version/markers.py index d432d9d48..b6b7f6519 100644 --- a/conda_lock/_vendor/poetry/core/version/markers.py +++ b/conda_lock/_vendor/poetry/core/version/markers.py @@ -1,24 +1,22 @@ -import os +from __future__ import annotations + +import itertools import re from typing import TYPE_CHECKING from typing import Any -from typing import Dict -from typing import Iterator -from typing import List -from typing import Union +from typing import Callable +from typing import Iterable -from lark import Lark -from lark import Token -from lark import Tree +from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint +from conda_lock._vendor.poetry.core.version.grammars import GRAMMAR_PEP_508_MARKERS +from conda_lock._vendor.poetry.core.version.parser import Parser if TYPE_CHECKING: - from conda_lock._vendor.poetry.core.semver import VersionTypes # noqa + from lark import Tree -MarkerTypes = Union[ - "AnyMarker", "EmptyMarker", "SingleMarker", "MultiMarker", "MarkerUnion" -] + from conda_lock._vendor.poetry.core.constraints.generic import BaseConstraint class InvalidMarker(ValueError): @@ -48,81 +46,83 @@ class UndefinedEnvironmentName(ValueError): "platform.python_implementation": "platform_python_implementation", "python_implementation": "platform_python_implementation", } -_parser = Lark.open( - os.path.join(os.path.dirname(__file__), "grammars", "markers.lark"), parser="lalr" -) + +PYTHON_VERSION_MARKERS = {"python_version", "python_full_version"} + +# Parser: PEP 508 Environment Markers +_parser = Parser(GRAMMAR_PEP_508_MARKERS, "lalr") -class BaseMarker(object): - def intersect(self, other): # type: (BaseMarker) -> BaseMarker +class BaseMarker: + def intersect(self, other: BaseMarker) -> BaseMarker: raise NotImplementedError() - def union(self, other): # type: (BaseMarker) -> BaseMarker + def union(self, other: BaseMarker) -> BaseMarker: raise NotImplementedError() - def is_any(self): # type: () -> bool + def is_any(self) -> bool: return False - def is_empty(self): # type: () -> bool + def is_empty(self) -> bool: return False - def validate(self, environment): # type: (Dict[str, Any]) -> bool + def validate(self, environment: dict[str, Any] | None) -> bool: raise NotImplementedError() - def without_extras(self): # type: () -> BaseMarker + def without_extras(self) -> BaseMarker: raise NotImplementedError() - def exclude(self, marker_name): # type: (str) -> BaseMarker + def exclude(self, marker_name: str) -> BaseMarker: raise NotImplementedError() - def only(self, *marker_names): # type: (str) -> BaseMarker + def only(self, *marker_names: str) -> BaseMarker: raise NotImplementedError() - def invert(self): # type: () -> BaseMarker + def invert(self) -> BaseMarker: raise NotImplementedError() - def __repr__(self): # type: () -> str - return "<{} {}>".format(self.__class__.__name__, str(self)) + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {str(self)}>" class AnyMarker(BaseMarker): - def intersect(self, other): # type: (MarkerTypes) -> MarkerTypes + def intersect(self, other: BaseMarker) -> BaseMarker: return other - def union(self, other): # type: (MarkerTypes) -> MarkerTypes + def union(self, other: BaseMarker) -> BaseMarker: return self - def is_any(self): # type: () -> bool + def is_any(self) -> bool: return True - def is_empty(self): # type: () -> bool + def is_empty(self) -> bool: return False - def validate(self, environment): # type: (Dict[str, Any]) -> bool + def validate(self, environment: dict[str, Any] | None) -> bool: return True - def without_extras(self): # type: () -> MarkerTypes + def without_extras(self) -> BaseMarker: return self - def exclude(self, marker_name): # type: (str) -> MarkerTypes + def exclude(self, marker_name: str) -> BaseMarker: return self - def only(self, *marker_names): # type: (*str) -> MarkerTypes + def only(self, *marker_names: str) -> BaseMarker: return self - def invert(self): # type: () -> EmptyMarker + def invert(self) -> EmptyMarker: return EmptyMarker() - def __str__(self): # type: () -> str + def __str__(self) -> str: return "" - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "" - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(("", "")) - def __eq__(self, other): # type: (MarkerTypes) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, BaseMarker): return NotImplemented @@ -130,43 +130,43 @@ def __eq__(self, other): # type: (MarkerTypes) -> bool class EmptyMarker(BaseMarker): - def intersect(self, other): # type: (MarkerTypes) -> MarkerTypes + def intersect(self, other: BaseMarker) -> BaseMarker: return self - def union(self, other): # type: (MarkerTypes) -> MarkerTypes + def union(self, other: BaseMarker) -> BaseMarker: return other - def is_any(self): # type: () -> bool + def is_any(self) -> bool: return False - def is_empty(self): # type: () -> bool + def is_empty(self) -> bool: return True - def validate(self, environment): # type: (Dict[str, Any]) -> bool + def validate(self, environment: dict[str, Any] | None) -> bool: return False - def without_extras(self): # type: () -> BaseMarker + def without_extras(self) -> BaseMarker: return self - def exclude(self, marker_name): # type: (str) -> EmptyMarker + def exclude(self, marker_name: str) -> EmptyMarker: return self - def only(self, *marker_names): # type: (*str) -> EmptyMarker + def only(self, *marker_names: str) -> EmptyMarker: return self - def invert(self): # type: () -> AnyMarker + def invert(self) -> AnyMarker: return AnyMarker() - def __str__(self): # type: () -> str + def __str__(self) -> str: return "" - def __repr__(self): # type: () -> str + def __repr__(self) -> str: return "" - def __hash__(self): # type: () -> int + def __hash__(self) -> int: return hash(("", "")) - def __eq__(self, other): # type: (MarkerTypes) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, BaseMarker): return NotImplemented @@ -174,7 +174,6 @@ def __eq__(self, other): # type: (MarkerTypes) -> bool class SingleMarker(BaseMarker): - _CONSTRAINT_RE = re.compile(r"(?i)^(~=|!=|>=?|<=?|==?=?|in|not in)?\s*(.+)$") _VERSION_LIKE_MARKER_NAME = { "python_version", @@ -183,18 +182,25 @@ class SingleMarker(BaseMarker): } def __init__( - self, name, constraint - ): # type: (str, Union[str, "VersionTypes"]) -> None - from conda_lock._vendor.poetry.core.packages.constraints import ( + self, name: str, constraint: str | BaseConstraint | VersionConstraint + ) -> None: + from conda_lock._vendor.poetry.core.constraints.generic import ( parse_constraint as parse_generic_constraint, ) - from conda_lock._vendor.poetry.core.semver import parse_constraint + from conda_lock._vendor.poetry.core.constraints.version import ( + parse_constraint as parse_version_constraint, + ) + self._constraint: BaseConstraint | VersionConstraint + self._parser: Callable[[str], BaseConstraint | VersionConstraint] self._name = ALIASES.get(name, name) - self._constraint_string = str(constraint) + constraint_string = str(constraint) # Extract operator and value - m = self._CONSTRAINT_RE.match(self._constraint_string) + m = self._CONSTRAINT_RE.match(constraint_string) + if m is None: + raise InvalidMarker(f"Invalid marker '{constraint_string}'") + self._operator = m.group(1) if self._operator is None: self._operator = "==" @@ -203,7 +209,7 @@ def __init__( self._parser = parse_generic_constraint if name in self._VERSION_LIKE_MARKER_NAME: - self._parser = parse_constraint + self._parser = parse_version_constraint if self._operator in {"in", "not in"}: versions = [] @@ -223,99 +229,80 @@ def __init__( self._constraint = self._parser(glue.join(versions)) else: - self._constraint = self._parser(self._constraint_string) + self._constraint = self._parser(constraint_string) else: # if we have a in/not in operator we split the constraint # into a union/multi-constraint of single constraint - constraint_string = self._constraint_string if self._operator in {"in", "not in"}: op, glue = ("==", " || ") if self._operator == "in" else ("!=", ", ") values = re.split("[ ,]+", self._value) - constraint_string = glue.join( - ("{} {}".format(op, value) for value in values) - ) + constraint_string = glue.join(f"{op} {value}" for value in values) self._constraint = self._parser(constraint_string) @property - def name(self): # type: () -> str + def name(self) -> str: return self._name @property - def constraint_string(self): # type: () -> str - if self._operator in {"in", "not in"}: - return "{} {}".format(self._operator, self._value) - - return self._constraint_string - - @property - def constraint(self): # type: () -> "VersionTypes" + def constraint(self) -> BaseConstraint | VersionConstraint: return self._constraint @property - def operator(self): # type: () -> str + def operator(self) -> str: return self._operator @property - def value(self): # type: () -> str + def value(self) -> str: return self._value - def intersect(self, other): # type: (MarkerTypes) -> MarkerTypes + def intersect(self, other: BaseMarker) -> BaseMarker: if isinstance(other, SingleMarker): - if other.name != self.name: - return MultiMarker(self, other) - - if self == other: - return self - - if self._operator in {"in", "not in"} or other.operator in {"in", "not in"}: - return MultiMarker.of(self, other) - - new_constraint = self._constraint.intersect(other.constraint) - if new_constraint.is_empty(): - return EmptyMarker() - - if new_constraint == self._constraint or new_constraint == other.constraint: - return SingleMarker(self._name, new_constraint) - return MultiMarker.of(self, other) return other.intersect(self) - def union(self, other): # type: (MarkerTypes) -> MarkerTypes + def union(self, other: BaseMarker) -> BaseMarker: if isinstance(other, SingleMarker): if self == other: return self + if self == other.invert(): + return AnyMarker() + return MarkerUnion.of(self, other) return other.union(self) - def validate(self, environment): # type: (Dict[str, Any]) -> bool + def validate(self, environment: dict[str, Any] | None) -> bool: if environment is None: return True if self._name not in environment: return True - return self._constraint.allows(self._parser(environment[self._name])) + # The type of constraint returned by the parser matches our constraint: either + # both are BaseConstraint or both are VersionConstraint. But it's hard for mypy + # to know that. + constraint = self._parser(environment[self._name]) + return self._constraint.allows(constraint) # type: ignore[arg-type] - def without_extras(self): # type: () -> MarkerTypes + def without_extras(self) -> BaseMarker: return self.exclude("extra") - def exclude(self, marker_name): # type: (str) -> MarkerTypes + def exclude(self, marker_name: str) -> BaseMarker: if self.name == marker_name: return AnyMarker() return self - def only(self, *marker_names): # type: (*str) -> Union[SingleMarker, EmptyMarker] + def only(self, *marker_names: str) -> SingleMarker | AnyMarker: if self.name not in marker_names: - return EmptyMarker() + return AnyMarker() return self - def invert(self): # type: () -> MarkerTypes + def invert(self) -> BaseMarker: if self._operator in ("===", "=="): operator = "!=" elif self._operator == "!=": @@ -336,9 +323,9 @@ def invert(self): # type: () -> MarkerTypes # This one is more tricky to handle # since it's technically a multi marker # so the inverse will be a union of inverse - from conda_lock._vendor.poetry.core.semver import VersionRange + from conda_lock._vendor.poetry.core.constraints.version import VersionRangeConstraint - if not isinstance(self._constraint, VersionRange): + if not isinstance(self._constraint, VersionRangeConstraint): # The constraint must be a version range, otherwise # it's an internal error raise RuntimeError( @@ -346,41 +333,45 @@ def invert(self): # type: () -> MarkerTypes ) min_ = self._constraint.min - min_operator = ">=" if self._constraint.include_min else "<" + min_operator = ">=" if self._constraint.include_min else ">" max_ = self._constraint.max max_operator = "<=" if self._constraint.include_max else "<" return MultiMarker.of( - SingleMarker(self._name, "{} {}".format(min_operator, min_)), - SingleMarker(self._name, "{} {}".format(max_operator, max_)), + SingleMarker(self._name, f"{min_operator} {min_}"), + SingleMarker(self._name, f"{max_operator} {max_}"), ).invert() else: # We should never go there - raise RuntimeError("Invalid marker operator '{}'".format(self._operator)) + raise RuntimeError(f"Invalid marker operator '{self._operator}'") - return parse_marker("{} {} '{}'".format(self._name, operator, self._value)) + return parse_marker(f"{self._name} {operator} '{self._value}'") - def __eq__(self, other): # type: (MarkerTypes) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, SingleMarker): return False return self._name == other.name and self._constraint == other.constraint - def __hash__(self): # type: () -> int - return hash((self._name, self._constraint_string)) + def __hash__(self) -> int: + return hash((self._name, self._constraint)) - def __str__(self): # type: () -> str - return '{} {} "{}"'.format(self._name, self._operator, self._value) + def __str__(self) -> str: + return f'{self._name} {self._operator} "{self._value}"' def _flatten_markers( - markers, flatten_class -): # type: (Iterator[Union[MarkerUnion, MultiMarker]], Any) -> List[MarkerTypes] + markers: Iterable[BaseMarker], + flatten_class: type[MarkerUnion | MultiMarker], +) -> list[BaseMarker]: flattened = [] for marker in markers: if isinstance(marker, flatten_class): - flattened += _flatten_markers(marker.markers, flatten_class) + flattened += _flatten_markers( + marker.markers, # type: ignore[attr-defined] + flatten_class, + ) else: flattened.append(marker) @@ -388,90 +379,172 @@ def _flatten_markers( class MultiMarker(BaseMarker): - def __init__(self, *markers): # type: (*MarkerTypes) -> None + def __init__(self, *markers: BaseMarker) -> None: self._markers = [] - markers = _flatten_markers(markers, MultiMarker) + flattened_markers = _flatten_markers(markers, MultiMarker) - for m in markers: + for m in flattened_markers: self._markers.append(m) @classmethod - def of(cls, *markers): # type: (*MarkerTypes) -> MarkerTypes - new_markers = [] - markers = _flatten_markers(markers, MultiMarker) - - for marker in markers: - if marker in new_markers: - continue + def of(cls, *markers: BaseMarker) -> BaseMarker: + new_markers = _flatten_markers(markers, MultiMarker) + old_markers: list[BaseMarker] = [] + + while old_markers != new_markers: + old_markers = new_markers + new_markers = [] + for marker in old_markers: + if marker in new_markers: + continue - if marker.is_any(): - continue + if marker.is_any(): + continue - if isinstance(marker, SingleMarker): - intersected = False - for i, mark in enumerate(new_markers): - if ( - not isinstance(mark, SingleMarker) - or isinstance(mark, SingleMarker) - and mark.name != marker.name - ): + if isinstance(marker, SingleMarker): + intersected = False + for i, mark in enumerate(new_markers): + if isinstance(mark, SingleMarker) and ( + mark.name == marker.name + or {mark.name, marker.name} == PYTHON_VERSION_MARKERS + ): + new_marker = _merge_single_markers(mark, marker, cls) + if new_marker is not None: + new_markers[i] = new_marker + intersected = True + + elif isinstance(mark, MarkerUnion): + intersection = mark.intersect(marker) + if isinstance(intersection, SingleMarker): + new_markers[i] = intersection + elif intersection.is_empty(): + return EmptyMarker() + if intersected: continue - intersection = mark.constraint.intersect(marker.constraint) - if intersection == mark.constraint: - intersected = True - elif intersection == marker.constraint: - new_markers[i] = marker - intersected = True - elif intersection.is_empty(): - return EmptyMarker() - - if intersected: - continue + elif isinstance(marker, MarkerUnion): + for mark in new_markers: + if isinstance(mark, SingleMarker): + intersection = marker.intersect(mark) + if isinstance(intersection, SingleMarker): + marker = intersection + break + elif intersection.is_empty(): + return EmptyMarker() - new_markers.append(marker) + new_markers.append(marker) if any(m.is_empty() for m in new_markers) or not new_markers: return EmptyMarker() - if len(new_markers) == 1 and new_markers[0].is_any(): - return AnyMarker() + if len(new_markers) == 1: + return new_markers[0] return MultiMarker(*new_markers) @property - def markers(self): # type: () -> List[MarkerTypes] + def markers(self) -> list[BaseMarker]: return self._markers - def intersect(self, other): # type: (MarkerTypes) -> MarkerTypes + def intersect(self, other: BaseMarker) -> BaseMarker: if other.is_any(): return self if other.is_empty(): return other + if isinstance(other, MarkerUnion): + return other.intersect(self) + new_markers = self._markers + [other] return MultiMarker.of(*new_markers) - def union(self, other): # type: (MarkerTypes) -> MarkerTypes + def union(self, other: BaseMarker) -> BaseMarker: if isinstance(other, (SingleMarker, MultiMarker)): return MarkerUnion.of(self, other) return other.union(self) - def validate(self, environment): # type: (Dict[str, Any]) -> bool - for m in self._markers: - if not m.validate(environment): - return False + def union_simplify(self, other: BaseMarker) -> BaseMarker | None: + """ + In contrast to the standard union method, which prefers to return + a MarkerUnion of MultiMarkers, this version prefers to return + a MultiMarker of MarkerUnions. - return True + The rationale behind this approach is to find additional simplifications. + In order to avoid endless recursions, this method returns None + if it cannot find a simplification. + """ + if isinstance(other, SingleMarker): + new_markers = [] + for marker in self._markers: + union = marker.union(other) + if not union.is_any(): + new_markers.append(union) + + if len(new_markers) == 1: + return new_markers[0] + if other in new_markers and all( + other == m or isinstance(m, MarkerUnion) and other in m.markers + for m in new_markers + ): + return other + + if not any(isinstance(m, MarkerUnion) for m in new_markers): + return self.of(*new_markers) + + elif isinstance(other, MultiMarker): + common_markers = [ + marker for marker in self.markers if marker in other.markers + ] + + unique_markers = [ + marker for marker in self.markers if marker not in common_markers + ] + if not unique_markers: + return self + + other_unique_markers = [ + marker for marker in other.markers if marker not in common_markers + ] + if not other_unique_markers: + return other + + if common_markers: + unique_union = self.of(*unique_markers).union( + self.of(*other_unique_markers) + ) + if not isinstance(unique_union, MarkerUnion): + return self.of(*common_markers).intersect(unique_union) - def without_extras(self): # type: () -> MarkerTypes + else: + # Usually this operation just complicates things, but the special case + # where it doesn't allows the collapse of adjacent ranges eg + # + # 'python_version >= "3.6" and python_version < "3.6.2"' union + # 'python_version >= "3.6.2" and python_version < "3.7"' -> + # + # 'python_version >= "3.6" and python_version < "3.7"'. + unions = [ + m1.union(m2) for m2 in other_unique_markers for m1 in unique_markers + ] + conjunction = self.of(*unions) + if not isinstance(conjunction, MultiMarker) or not any( + isinstance(m, MarkerUnion) for m in conjunction.markers + ): + return conjunction + + return None + + def validate(self, environment: dict[str, Any] | None) -> bool: + return all(m.validate(environment) for m in self._markers) + + def without_extras(self) -> BaseMarker: return self.exclude("extra") - def exclude(self, marker_name): # type: (str) -> MarkerTypes + def exclude(self, marker_name: str) -> BaseMarker: new_markers = [] for m in self._markers: @@ -486,7 +559,7 @@ def exclude(self, marker_name): # type: (str) -> MarkerTypes return self.of(*new_markers) - def only(self, *marker_names): # type: (*str) -> MarkerTypes + def only(self, *marker_names: str) -> BaseMarker: new_markers = [] for m in self._markers: @@ -501,96 +574,109 @@ def only(self, *marker_names): # type: (*str) -> MarkerTypes return self.of(*new_markers) - def invert(self): # type: () -> MarkerTypes + def invert(self) -> BaseMarker: markers = [marker.invert() for marker in self._markers] return MarkerUnion.of(*markers) - def __eq__(self, other): # type: (MarkerTypes) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, MultiMarker): return False return set(self._markers) == set(other.markers) - def __hash__(self): # type: () -> int + def __hash__(self) -> int: h = hash("multi") for m in self._markers: - h |= hash(m) + h ^= hash(m) return h - def __str__(self): # type: () -> str + def __str__(self) -> str: elements = [] for m in self._markers: - if isinstance(m, SingleMarker): - elements.append(str(m)) - elif isinstance(m, MultiMarker): + if isinstance(m, (SingleMarker, MultiMarker)): elements.append(str(m)) else: - elements.append("({})".format(str(m))) + elements.append(f"({str(m)})") return " and ".join(elements) class MarkerUnion(BaseMarker): - def __init__(self, *markers): # type: (*MarkerTypes) -> None + def __init__(self, *markers: BaseMarker) -> None: self._markers = list(markers) @property - def markers(self): # type: () -> List[MarkerTypes] + def markers(self) -> list[BaseMarker]: return self._markers @classmethod - def of(cls, *markers): # type: (*BaseMarker) -> MarkerTypes - flattened_markers = _flatten_markers(markers, MarkerUnion) - - markers = [] - for marker in flattened_markers: - if marker in markers: - continue - - if isinstance(marker, SingleMarker) and marker.name == "python_version": - intersected = False - for i, mark in enumerate(markers): - if ( - not isinstance(mark, SingleMarker) - or isinstance(mark, SingleMarker) - and mark.name != marker.name - ): - continue - - intersection = mark.constraint.union(marker.constraint) - if intersection == mark.constraint: - intersected = True - break - elif intersection == marker.constraint: - markers[i] = marker - intersected = True - break - - if intersected: + def of(cls, *markers: BaseMarker) -> BaseMarker: + new_markers = _flatten_markers(markers, MarkerUnion) + old_markers: list[BaseMarker] = [] + + while old_markers != new_markers: + old_markers = new_markers + new_markers = [] + for marker in old_markers: + if marker in new_markers or marker.is_empty(): continue - markers.append(marker) - - if any(m.is_any() for m in markers): + included = False + + if isinstance(marker, SingleMarker): + for i, mark in enumerate(new_markers): + if isinstance(mark, SingleMarker) and ( + mark.name == marker.name + or {mark.name, marker.name} == PYTHON_VERSION_MARKERS + ): + new_marker = _merge_single_markers(mark, marker, cls) + if new_marker is not None: + new_markers[i] = new_marker + included = True + break + + elif isinstance(mark, MultiMarker): + union = mark.union_simplify(marker) + if union is not None: + new_markers[i] = union + included = True + break + + elif isinstance(marker, MultiMarker): + included = False + for i, mark in enumerate(new_markers): + union = marker.union_simplify(mark) + if union is not None: + new_markers[i] = union + included = True + break + + if included: + # flatten again because union_simplify may return a union + new_markers = _flatten_markers(new_markers, MarkerUnion) + else: + new_markers.append(marker) + + if any(m.is_any() for m in new_markers): return AnyMarker() - if not markers: - return AnyMarker() + if not new_markers: + return EmptyMarker() - if len(markers) == 1: - return markers[0] + if len(new_markers) == 1: + return new_markers[0] - return MarkerUnion(*markers) + return MarkerUnion(*new_markers) - def append(self, marker): # type: (MarkerTypes) -> None + def append(self, marker: BaseMarker) -> None: if marker in self._markers: return self._markers.append(marker) - def intersect(self, other): # type: (MarkerTypes) -> MarkerTypes + def intersect(self, other: BaseMarker) -> BaseMarker: if other.is_any(): return self @@ -614,7 +700,7 @@ def intersect(self, other): # type: (MarkerTypes) -> MarkerTypes return MarkerUnion.of(*new_markers) - def union(self, other): # type: (MarkerTypes) -> MarkerTypes + def union(self, other: BaseMarker) -> BaseMarker: if other.is_any(): return other @@ -625,17 +711,13 @@ def union(self, other): # type: (MarkerTypes) -> MarkerTypes return MarkerUnion.of(*new_markers) - def validate(self, environment): # type: (Dict[str, Any]) -> bool - for m in self._markers: - if m.validate(environment): - return True - - return False + def validate(self, environment: dict[str, Any] | None) -> bool: + return any(m.validate(environment) for m in self._markers) - def without_extras(self): # type: () -> MarkerTypes + def without_extras(self) -> BaseMarker: return self.exclude("extra") - def exclude(self, marker_name): # type: (str) -> MarkerTypes + def exclude(self, marker_name: str) -> BaseMarker: new_markers = [] for m in self._markers: @@ -644,13 +726,15 @@ def exclude(self, marker_name): # type: (str) -> MarkerTypes continue marker = m.exclude(marker_name) + new_markers.append(marker) - if not marker.is_empty(): - new_markers.append(marker) + if not new_markers: + # All markers were the excluded marker. + return AnyMarker() return self.of(*new_markers) - def only(self, *marker_names): # type: (*str) -> MarkerTypes + def only(self, *marker_names: str) -> BaseMarker: new_markers = [] for m in self._markers: @@ -665,37 +749,37 @@ def only(self, *marker_names): # type: (*str) -> MarkerTypes return self.of(*new_markers) - def invert(self): # type: () -> MarkerTypes + def invert(self) -> BaseMarker: markers = [marker.invert() for marker in self._markers] return MultiMarker.of(*markers) - def __eq__(self, other): # type: (MarkerTypes) -> bool + def __eq__(self, other: object) -> bool: if not isinstance(other, MarkerUnion): return False return set(self._markers) == set(other.markers) - def __hash__(self): # type: () -> int + def __hash__(self) -> int: h = hash("union") for m in self._markers: - h |= hash(m) + h ^= hash(m) return h - def __str__(self): # type: () -> str + def __str__(self) -> str: return " or ".join( str(m) for m in self._markers if not m.is_any() and not m.is_empty() ) - def is_any(self): # type: () -> bool + def is_any(self) -> bool: return any(m.is_any() for m in self._markers) - def is_empty(self): # type: () -> bool + def is_empty(self) -> bool: return all(m.is_empty() for m in self._markers) -def parse_marker(marker): # type: (str) -> MarkerTypes +def parse_marker(marker: str) -> BaseMarker: if marker == "": return EmptyMarker() @@ -709,11 +793,13 @@ def parse_marker(marker): # type: (str) -> MarkerTypes return markers -def _compact_markers(tree_elements, tree_prefix=""): # type: (Tree, str) -> MarkerTypes - groups = [MultiMarker()] +def _compact_markers(tree_elements: Tree, tree_prefix: str = "") -> BaseMarker: + from lark import Token + + groups: list[BaseMarker] = [MultiMarker()] for token in tree_elements: if isinstance(token, Token): - if token.type == "{}BOOL_OP".format(tree_prefix) and token.value == "or": + if token.type == f"{tree_prefix}BOOL_OP" and token.value == "or": groups.append(MultiMarker()) continue @@ -722,18 +808,20 @@ def _compact_markers(tree_elements, tree_prefix=""): # type: (Tree, str) -> Mar groups[-1] = MultiMarker.of( groups[-1], _compact_markers(token.children, tree_prefix=tree_prefix) ) - elif token.data == "{}item".format(tree_prefix): + elif token.data == f"{tree_prefix}item": name, op, value = token.children - if value.type == "{}MARKER_NAME".format(tree_prefix): - name, value, = value, name + if value.type == f"{tree_prefix}MARKER_NAME": + name, value, = ( + value, + name, + ) value = value[1:-1] groups[-1] = MultiMarker.of( - groups[-1], SingleMarker(name, "{}{}".format(op, value)) + groups[-1], SingleMarker(str(name), f"{op}{value}") ) - elif token.data == "{}BOOL_OP".format(tree_prefix): - if token.children[0] == "or": - groups.append(MultiMarker()) + elif token.data == f"{tree_prefix}BOOL_OP" and token.children[0] == "or": + groups.append(MultiMarker()) for i, group in enumerate(reversed(groups)): if group.is_empty(): @@ -750,3 +838,89 @@ def _compact_markers(tree_elements, tree_prefix=""): # type: (Tree, str) -> Mar return groups[0] return MarkerUnion.of(*groups) + + +def dnf(marker: BaseMarker) -> BaseMarker: + """Transforms the marker into DNF (disjunctive normal form).""" + if isinstance(marker, MultiMarker): + dnf_markers = [dnf(m) for m in marker.markers] + sub_marker_lists = [ + m.markers if isinstance(m, MarkerUnion) else [m] for m in dnf_markers + ] + return MarkerUnion.of( + *[MultiMarker.of(*c) for c in itertools.product(*sub_marker_lists)] + ) + if isinstance(marker, MarkerUnion): + return MarkerUnion.of(*[dnf(m) for m in marker.markers]) + return marker + + +def _merge_single_markers( + marker1: SingleMarker, + marker2: SingleMarker, + merge_class: type[MultiMarker | MarkerUnion], +) -> BaseMarker | None: + if {marker1.name, marker2.name} == PYTHON_VERSION_MARKERS: + return _merge_python_version_single_markers(marker1, marker2, merge_class) + + if merge_class == MultiMarker: + merge_method = marker1.constraint.intersect + else: + merge_method = marker1.constraint.union + # Markers with the same name have the same constraint type, + # but mypy can't see that. + result_constraint = merge_method(marker2.constraint) # type: ignore[arg-type] + + result_marker: BaseMarker | None = None + if result_constraint.is_empty(): + result_marker = EmptyMarker() + elif result_constraint.is_any(): + result_marker = AnyMarker() + elif result_constraint == marker1.constraint: + result_marker = marker1 + elif result_constraint == marker2.constraint: + result_marker = marker2 + elif ( + isinstance(result_constraint, VersionConstraint) + and result_constraint.is_simple() + ): + result_marker = SingleMarker(marker1.name, result_constraint) + return result_marker + + +def _merge_python_version_single_markers( + marker1: SingleMarker, + marker2: SingleMarker, + merge_class: type[MultiMarker | MarkerUnion], +) -> BaseMarker | None: + from conda_lock._vendor.poetry.core.packages.utils.utils import get_python_constraint_from_marker + + if marker1.name == "python_version": + version_marker = marker1 + full_version_marker = marker2 + else: + version_marker = marker2 + full_version_marker = marker1 + + normalized_constraint = get_python_constraint_from_marker(version_marker) + normalized_marker = SingleMarker("python_full_version", normalized_constraint) + merged_marker = _merge_single_markers( + normalized_marker, full_version_marker, merge_class + ) + if merged_marker == normalized_marker: + # prefer original marker to avoid unnecessary changes + return version_marker + if merged_marker and isinstance(merged_marker, SingleMarker): + # We have to fix markers like 'python_full_version == "3.6"' + # to receive 'python_full_version == "3.6.0"'. + # It seems a bit hacky to convert to string and back to marker, + # but it's probably much simpler than to consider the different constraint + # classes (mostly VersonRangeConstraint, but VersionUnion for "!=") and + # since this conversion is only required for python_full_version markers + # it may be sufficient to handle it here. + marker_string = str(merged_marker) + precision = marker_string.count(".") + 1 + if precision < 3: + marker_string = marker_string[:-1] + ".0" * (3 - precision) + '"' + merged_marker = parse_marker(marker_string) + return merged_marker diff --git a/conda_lock/_vendor/poetry/core/version/parser.py b/conda_lock/_vendor/poetry/core/version/parser.py new file mode 100644 index 000000000..085cfa384 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/version/parser.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +from typing import Any + + +if TYPE_CHECKING: + from pathlib import Path + + from lark import Lark + from lark import Tree + + +class Parser: + def __init__( + self, grammar: Path, parser: str = "lalr", debug: bool = False + ) -> None: + self._grammar = grammar + self._parser = parser + self._debug = debug + self._lark: Lark | None = None + + def parse(self, text: str, **kwargs: Any) -> Tree: + from lark import Lark + + if self._lark is None: + self._lark = Lark.open( + grammar_filename=self._grammar, parser=self._parser, debug=self._debug + ) + + return self._lark.parse(text=text, **kwargs) diff --git a/conda_lock/_vendor/poetry/core/version/pep440/__init__.py b/conda_lock/_vendor/poetry/core/version/pep440/__init__.py new file mode 100644 index 000000000..a832000f0 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/version/pep440/__init__.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from conda_lock._vendor.poetry.core.version.pep440.segments import LocalSegmentType +from conda_lock._vendor.poetry.core.version.pep440.segments import Release +from conda_lock._vendor.poetry.core.version.pep440.segments import ReleaseTag +from conda_lock._vendor.poetry.core.version.pep440.version import PEP440Version + + +__all__ = ["LocalSegmentType", "Release", "ReleaseTag", "PEP440Version"] diff --git a/conda_lock/_vendor/poetry/core/version/pep440/parser.py b/conda_lock/_vendor/poetry/core/version/pep440/parser.py new file mode 100644 index 000000000..eb1ccd1d4 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/version/pep440/parser.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import re + +from typing import TYPE_CHECKING +from typing import Match +from typing import TypeVar + +from packaging.version import VERSION_PATTERN + +from conda_lock._vendor.poetry.core.version.exceptions import InvalidVersion +from conda_lock._vendor.poetry.core.version.pep440 import Release +from conda_lock._vendor.poetry.core.version.pep440 import ReleaseTag + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.version.pep440 import LocalSegmentType + from conda_lock._vendor.poetry.core.version.pep440.version import PEP440Version + +T = TypeVar("T", bound="PEP440Version") + + +class PEP440Parser: + _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) + _local_version_separators = re.compile(r"[._-]") + + @classmethod + def _get_release(cls, match: Match[str] | None) -> Release: + if not match or match.group("release") is None: + return Release(0) + return Release.from_parts(*(int(i) for i in match.group("release").split("."))) + + @classmethod + def _get_prerelease(cls, match: Match[str] | None) -> ReleaseTag | None: + if not match or match.group("pre") is None: + return None + return ReleaseTag(match.group("pre_l"), int(match.group("pre_n") or 0)) + + @classmethod + def _get_postrelease(cls, match: Match[str] | None) -> ReleaseTag | None: + if not match or match.group("post") is None: + return None + + return ReleaseTag( + match.group("post_l") or "post", + int(match.group("post_n1") or match.group("post_n2") or 0), + ) + + @classmethod + def _get_devrelease(cls, match: Match[str] | None) -> ReleaseTag | None: + if not match or match.group("dev") is None: + return None + return ReleaseTag(match.group("dev_l"), int(match.group("dev_n") or 0)) + + @classmethod + def _get_local(cls, match: Match[str] | None) -> LocalSegmentType | None: + if not match or match.group("local") is None: + return None + + return tuple( + part.lower() + for part in cls._local_version_separators.split(match.group("local")) + ) + + @classmethod + def parse(cls, value: str, version_class: type[T]) -> T: + match = cls._regex.search(value) if value else None + if not match: + raise InvalidVersion(f"Invalid PEP 440 version: '{value}'") + + return version_class( + epoch=int(match.group("epoch")) if match.group("epoch") else 0, + release=cls._get_release(match), + pre=cls._get_prerelease(match), + post=cls._get_postrelease(match), + dev=cls._get_devrelease(match), + local=cls._get_local(match), + text=value, + ) + + +def parse_pep440(value: str, version_class: type[T]) -> T: + return PEP440Parser.parse(value, version_class) diff --git a/conda_lock/_vendor/poetry/core/version/pep440/segments.py b/conda_lock/_vendor/poetry/core/version/pep440/segments.py new file mode 100644 index 000000000..735f523e8 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/version/pep440/segments.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import dataclasses + +from typing import Optional +from typing import Tuple +from typing import Union + + +# Release phase IDs according to PEP440 +RELEASE_PHASE_ID_ALPHA = "a" +RELEASE_PHASE_ID_BETA = "b" +RELEASE_PHASE_ID_RC = "rc" +RELEASE_PHASE_ID_POST = "post" +RELEASE_PHASE_ID_DEV = "dev" + +RELEASE_PHASE_SPELLINGS = { + RELEASE_PHASE_ID_ALPHA: {RELEASE_PHASE_ID_ALPHA, "alpha"}, + RELEASE_PHASE_ID_BETA: {RELEASE_PHASE_ID_BETA, "beta"}, + RELEASE_PHASE_ID_RC: {RELEASE_PHASE_ID_RC, "c", "pre", "preview"}, + RELEASE_PHASE_ID_POST: {RELEASE_PHASE_ID_POST, "r", "rev", "-"}, + RELEASE_PHASE_ID_DEV: {RELEASE_PHASE_ID_DEV}, +} +RELEASE_PHASE_NORMALIZATIONS = { + s: id_ for id_, spellings in RELEASE_PHASE_SPELLINGS.items() for s in spellings +} + + +@dataclasses.dataclass(frozen=True, eq=True, order=True) +class Release: + major: int = dataclasses.field(default=0, compare=False) + minor: int | None = dataclasses.field(default=None, compare=False) + patch: int | None = dataclasses.field(default=None, compare=False) + # some projects use non-semver versioning schemes, eg: 1.2.3.4 + extra: tuple[int, ...] = dataclasses.field(default=(), compare=False) + precision: int = dataclasses.field(init=False, compare=False) + text: str = dataclasses.field(init=False, compare=False) + _compare_key: tuple[int, ...] = dataclasses.field(init=False, compare=True) + + def __post_init__(self) -> None: + if self.extra: + if self.minor is None: + object.__setattr__(self, "minor", 0) + if self.patch is None: + object.__setattr__(self, "patch", 0) + parts = [ + str(part) + for part in [self.major, self.minor, self.patch, *self.extra] + if part is not None + ] + object.__setattr__(self, "text", ".".join(parts)) + object.__setattr__(self, "precision", len(parts)) + object.__setattr__( + self, + "_compare_key", + (self.major, self.minor or 0, self.patch or 0, *self.extra), + ) + + @classmethod + def from_parts(cls, *parts: int) -> Release: + if not parts: + return cls() + + return cls( + major=parts[0], + minor=parts[1] if len(parts) > 1 else None, + patch=parts[2] if len(parts) > 2 else None, + extra=parts[3:], + ) + + def to_string(self) -> str: + return self.text + + def next_major(self) -> Release: + return dataclasses.replace( + self, + major=self.major + 1, + minor=0 if self.minor is not None else None, + patch=0 if self.patch is not None else None, + extra=tuple(0 for _ in self.extra), + ) + + def next_minor(self) -> Release: + return dataclasses.replace( + self, + major=self.major, + minor=self.minor + 1 if self.minor is not None else 1, + patch=0 if self.patch is not None else None, + extra=tuple(0 for _ in self.extra), + ) + + def next_patch(self) -> Release: + return dataclasses.replace( + self, + major=self.major, + minor=self.minor if self.minor is not None else 0, + patch=self.patch + 1 if self.patch is not None else 1, + extra=tuple(0 for _ in self.extra), + ) + + +@dataclasses.dataclass(frozen=True, eq=True, order=True) +class ReleaseTag: + phase: str + number: int = dataclasses.field(default=0) + + def __post_init__(self) -> None: + object.__setattr__( + self, "phase", RELEASE_PHASE_NORMALIZATIONS.get(self.phase, self.phase) + ) + + def to_string(self, short: bool = False) -> str: + if short: + import warnings + + warnings.warn( + "Parameter 'short' has no effect and will be removed. " + "(Release tags are always normalized according to PEP 440 now.)", + DeprecationWarning, + stacklevel=2, + ) + + return f"{self.phase}{self.number}" + + def next(self) -> ReleaseTag: + return dataclasses.replace(self, phase=self.phase, number=self.number + 1) + + def next_phase(self) -> ReleaseTag | None: + if self.phase in [ + RELEASE_PHASE_ID_POST, + RELEASE_PHASE_ID_RC, + RELEASE_PHASE_ID_DEV, + ]: + return None + + if self.phase == RELEASE_PHASE_ID_ALPHA: + _phase = RELEASE_PHASE_ID_BETA + elif self.phase == RELEASE_PHASE_ID_BETA: + _phase = RELEASE_PHASE_ID_RC + else: + return None + + return self.__class__(phase=_phase, number=0) + + +LocalSegmentType = Optional[Union[str, int, Tuple[Union[str, int], ...]]] diff --git a/conda_lock/_vendor/poetry/core/version/pep440/version.py b/conda_lock/_vendor/poetry/core/version/pep440/version.py new file mode 100644 index 000000000..cff86de40 --- /dev/null +++ b/conda_lock/_vendor/poetry/core/version/pep440/version.py @@ -0,0 +1,317 @@ +from __future__ import annotations + +import dataclasses +import functools +import warnings + +from typing import TYPE_CHECKING +from typing import Any +from typing import TypeVar + +from conda_lock._vendor.poetry.core.version.pep440.segments import RELEASE_PHASE_ID_ALPHA +from conda_lock._vendor.poetry.core.version.pep440.segments import RELEASE_PHASE_ID_DEV +from conda_lock._vendor.poetry.core.version.pep440.segments import RELEASE_PHASE_ID_POST +from conda_lock._vendor.poetry.core.version.pep440.segments import Release +from conda_lock._vendor.poetry.core.version.pep440.segments import ReleaseTag + + +if TYPE_CHECKING: + from conda_lock._vendor.poetry.core.version.pep440.segments import LocalSegmentType + + +@functools.total_ordering +class AlwaysSmaller: + def __lt__(self, other: object) -> bool: + return True + + +@functools.total_ordering +class AlwaysGreater: + def __gt__(self, other: object) -> bool: + return True + + +class Infinity(AlwaysGreater, int): + pass + + +class NegativeInfinity(AlwaysSmaller, int): + pass + + +T = TypeVar("T", bound="PEP440Version") + +# we use the phase "z" to ensure we always sort this after other phases +_INF_TAG = ReleaseTag("z", Infinity()) +# we use the phase "" to ensure we always sort this before other phases +_NEG_INF_TAG = ReleaseTag("", NegativeInfinity()) + + +@dataclasses.dataclass(frozen=True, eq=True, order=True) +class PEP440Version: + epoch: int = dataclasses.field(default=0, compare=False) + release: Release = dataclasses.field(default_factory=Release, compare=False) + pre: ReleaseTag | None = dataclasses.field(default=None, compare=False) + post: ReleaseTag | None = dataclasses.field(default=None, compare=False) + dev: ReleaseTag | None = dataclasses.field(default=None, compare=False) + local: LocalSegmentType = dataclasses.field(default=None, compare=False) + text: str = dataclasses.field(default="", compare=False) + _compare_key: tuple[ + int, Release, ReleaseTag, ReleaseTag, ReleaseTag, tuple[int | str, ...] + ] = dataclasses.field(init=False, compare=True) + + def __post_init__(self) -> None: + if self.local is not None and not isinstance(self.local, tuple): + object.__setattr__(self, "local", (self.local,)) + + if isinstance(self.release, tuple): + object.__setattr__(self, "release", Release(*self.release)) + + # we do this here to handle both None and tomlkit string values + object.__setattr__( + self, "text", self.to_string() if not self.text else str(self.text) + ) + + object.__setattr__(self, "_compare_key", self._make_compare_key()) + + def _make_compare_key( + self, + ) -> tuple[ + int, + Release, + ReleaseTag, + ReleaseTag, + ReleaseTag, + tuple[tuple[int, int | str], ...], + ]: + """ + This code is based on the implementation of packaging.version._cmpkey(..) + """ + # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. + # We'll do this by abusing the pre segment, but we _only_ want to do this + # if there is not a pre or a post segment. If we have one of those then + # the normal sorting rules will handle this case correctly. + if self.pre is None and self.post is None and self.dev is not None: + _pre = _NEG_INF_TAG + # Versions without a pre-release (except as noted above) should sort after + # those with one. + elif self.pre is None: + _pre = _INF_TAG + else: + _pre = self.pre + + # Versions without a post segment should sort before those with one. + _post = _NEG_INF_TAG if self.post is None else self.post + + # Versions without a development segment should sort after those with one. + _dev = _INF_TAG if self.dev is None else self.dev + + _local: tuple[tuple[int, int | str], ...] + if self.local is None: + # Versions without a local segment should sort before those with one. + _local = ((NegativeInfinity(), ""),) + else: + # Versions with a local segment need that segment parsed to implement + # the sorting rules in PEP440. + # - Alpha numeric segments sort before numeric segments + # - Alpha numeric segments sort lexicographically + # - Numeric segments sort numerically + # - Shorter versions sort before longer versions when the prefixes + # match exactly + assert isinstance(self.local, tuple) + _local = tuple( + # We typecast strings that are integers so that they can be compared + (int(i), "") if str(i).isnumeric() else (NegativeInfinity(), i) + for i in self.local + ) + return self.epoch, self.release, _pre, _post, _dev, _local + + @property + def major(self) -> int: + return self.release.major + + @property + def minor(self) -> int | None: + return self.release.minor + + @property + def patch(self) -> int | None: + return self.release.patch + + @property + def non_semver_parts(self) -> tuple[int, ...]: + assert isinstance(self.release.extra, tuple) + return self.release.extra + + def to_string(self, short: bool = False) -> str: + if short: + import warnings + + warnings.warn( + "Parameter 'short' has no effect and will be removed. " + "(Versions are always normalized according to PEP 440 now.)", + DeprecationWarning, + stacklevel=2, + ) + + version_string = self.release.to_string() + + if self.epoch: + # if epoch is non-zero we should include it + version_string = f"{self.epoch}!{version_string}" + + if self.pre: + version_string += self.pre.to_string() + + if self.post: + version_string = f"{version_string}.{self.post.to_string()}" + + if self.dev: + version_string = f"{version_string}.{self.dev.to_string()}" + + if self.local: + assert isinstance(self.local, tuple) + version_string += "+" + ".".join(map(str, self.local)) + + return version_string.lower() + + @classmethod + def parse(cls: type[T], value: str) -> T: + from conda_lock._vendor.poetry.core.version.pep440.parser import parse_pep440 + + return parse_pep440(value, cls) + + def is_prerelease(self) -> bool: + return self.pre is not None + + def is_postrelease(self) -> bool: + return self.post is not None + + def is_devrelease(self) -> bool: + return self.dev is not None + + def is_local(self) -> bool: + return self.local is not None + + def is_no_suffix_release(self) -> bool: + return not (self.pre or self.post or self.dev) + + def is_unstable(self) -> bool: + return self.is_prerelease() or self.is_devrelease() + + def is_stable(self) -> bool: + return not self.is_unstable() + + def _is_increment_required(self) -> bool: + return self.is_stable() or (not self.is_prerelease() and self.is_postrelease()) + + def next_major(self: T) -> T: + release = self.release + if self._is_increment_required() or Release(release.major, 0, 0) < release: + release = release.next_major() + return self.__class__(epoch=self.epoch, release=release) + + def next_minor(self: T) -> T: + release = self.release + if ( + self._is_increment_required() + or Release(release.major, release.minor, 0) < release + ): + release = release.next_minor() + return self.__class__(epoch=self.epoch, release=release) + + def next_patch(self: T) -> T: + release = self.release + if ( + self._is_increment_required() + or Release(release.major, release.minor, release.patch) < release + ): + release = release.next_patch() + return self.__class__(epoch=self.epoch, release=release) + + def next_prerelease(self: T, next_phase: bool = False) -> PEP440Version: + if self.is_stable(): + warnings.warn( + "Calling next_prerelease() on a stable release is deprecated for its" + " ambiguity. Use next_major(), next_minor(), etc. together with" + " first_prerelease()", + DeprecationWarning, + stacklevel=2, + ) + if self.is_prerelease(): + assert self.pre is not None + if not self.is_devrelease() or self.is_postrelease(): + pre = self.pre.next_phase() if next_phase else self.pre.next() + else: + pre = self.pre + else: + pre = ReleaseTag(RELEASE_PHASE_ID_ALPHA) + return self.__class__(epoch=self.epoch, release=self.release, pre=pre) + + def next_postrelease(self: T) -> T: + if self.is_postrelease(): + assert self.post is not None + post = self.post.next() if self.dev is None else self.post + else: + post = ReleaseTag(RELEASE_PHASE_ID_POST) + return self.__class__( + epoch=self.epoch, + release=self.release, + pre=self.pre, + post=post, + ) + + def next_devrelease(self: T) -> T: + if self.is_devrelease(): + assert self.dev is not None + dev = self.dev.next() + else: + warnings.warn( + "Calling next_devrelease() on a non dev release is deprecated for its" + " ambiguity. Use next_major(), next_minor(), etc. together with" + " first_devrelease()", + DeprecationWarning, + stacklevel=2, + ) + dev = ReleaseTag(RELEASE_PHASE_ID_DEV) + return self.__class__( + epoch=self.epoch, + release=self.release, + pre=self.pre, + post=self.post, + dev=dev, + ) + + def first_prerelease(self: T) -> T: + return self.__class__( + epoch=self.epoch, + release=self.release, + pre=ReleaseTag(RELEASE_PHASE_ID_ALPHA), + ) + + def first_devrelease(self: T) -> T: + return self.__class__( + epoch=self.epoch, + release=self.release, + pre=self.pre, + post=self.post, + dev=ReleaseTag(RELEASE_PHASE_ID_DEV), + ) + + def replace(self: T, **kwargs: Any) -> T: + return self.__class__( + **{ + **{ + k: getattr(self, k) + for k in self.__dataclass_fields__.keys() + if k not in ("_compare_key", "text") + }, # setup defaults with current values, excluding compare keys and text + **kwargs, # keys to replace + } + ) + + def without_local(self: T) -> T: + return self.replace(local=None) + + def without_postrelease(self: T) -> T: + return self.replace(post=None) diff --git a/conda_lock/_vendor/poetry/core/version/requirements.py b/conda_lock/_vendor/poetry/core/version/requirements.py index cdc7d016f..e9abbba08 100644 --- a/conda_lock/_vendor/poetry/core/version/requirements.py +++ b/conda_lock/_vendor/poetry/core/version/requirements.py @@ -1,26 +1,12 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +from __future__ import annotations -import os +import urllib.parse as urlparse -from lark import Lark -from lark import UnexpectedCharacters -from lark import UnexpectedToken - -from conda_lock._vendor.poetry.core.semver import parse_constraint -from conda_lock._vendor.poetry.core.semver.exceptions import ParseConstraintError - -from .markers import _compact_markers - - -try: - import urllib.parse as urlparse -except ImportError: - import urlparse +from conda_lock._vendor.poetry.core.constraints.version import parse_constraint +from conda_lock._vendor.poetry.core.constraints.version.exceptions import ParseConstraintError +from conda_lock._vendor.poetry.core.version.grammars import GRAMMAR_PEP_508_CONSTRAINTS +from conda_lock._vendor.poetry.core.version.markers import _compact_markers +from conda_lock._vendor.poetry.core.version.parser import Parser class InvalidRequirement(ValueError): @@ -29,12 +15,11 @@ class InvalidRequirement(ValueError): """ -_parser = Lark.open( - os.path.join(os.path.dirname(__file__), "grammars", "pep508.lark"), parser="lalr" -) +# Parser: PEP 508 Constraints +_parser = Parser(GRAMMAR_PEP_508_CONSTRAINTS, "lalr") -class Requirement(object): +class Requirement: """ Parse a requirement. @@ -43,17 +28,19 @@ class Requirement(object): string. """ - def __init__(self, requirement_string): # type: (str) -> None + def __init__(self, requirement_string: str) -> None: + from lark import UnexpectedCharacters + from lark import UnexpectedToken + try: parsed = _parser.parse(requirement_string) except (UnexpectedCharacters, UnexpectedToken) as e: raise InvalidRequirement( - "The requirement is invalid: Unexpected character at column {}\n\n{}".format( - e.column, e.get_context(requirement_string) - ) + "The requirement is invalid: Unexpected character at column" + f" {e.column}\n\n{e.get_context(requirement_string)}" ) - self.name = next(parsed.scan_values(lambda t: t.type == "NAME")).value + self.name: str = next(parsed.scan_values(lambda t: t.type == "NAME")).value url = next(parsed.scan_values(lambda t: t.type == "URI"), None) if url: @@ -62,14 +49,14 @@ def __init__(self, requirement_string): # type: (str) -> None if parsed_url.scheme == "file": if urlparse.urlunparse(parsed_url) != url: raise InvalidRequirement( - 'The requirement is invalid: invalid URL "{0}"'.format(url) + f'The requirement is invalid: invalid URL "{url}"' ) elif ( not (parsed_url.scheme and parsed_url.netloc) or (not parsed_url.scheme and not parsed_url.netloc) ) and not parsed_url.path: raise InvalidRequirement( - 'The requirement is invalid: invalid URL "{0}"'.format(url) + f'The requirement is invalid: invalid URL "{url}"' ) self.url = url else: @@ -86,9 +73,7 @@ def __init__(self, requirement_string): # type: (str) -> None self.constraint = parse_constraint(constraint) except ParseConstraintError: raise InvalidRequirement( - 'The requirement is invalid: invalid version constraint "{}"'.format( - constraint - ) + f'The requirement is invalid: invalid version constraint "{constraint}"' ) self.pretty_constraint = constraint @@ -101,22 +86,23 @@ def __init__(self, requirement_string): # type: (str) -> None self.marker = marker - def __str__(self): # type: () -> str + def __str__(self) -> str: parts = [self.name] if self.extras: - parts.append("[{0}]".format(",".join(sorted(self.extras)))) + extras = ",".join(sorted(self.extras)) + parts.append(f"[{extras}]") if self.pretty_constraint: parts.append(self.pretty_constraint) if self.url: - parts.append("@ {0}".format(self.url)) + parts.append(f"@ {self.url}") if self.marker: - parts.append("; {0}".format(self.marker)) + parts.append(f"; {self.marker}") return "".join(parts) - def __repr__(self): # type: () -> str - return "".format(str(self)) + def __repr__(self) -> str: + return f"" diff --git a/conda_lock/_vendor/poetry/core/version/utils.py b/conda_lock/_vendor/poetry/core/version/utils.py deleted file mode 100644 index a81a9e7f2..000000000 --- a/conda_lock/_vendor/poetry/core/version/utils.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Any - - -class Infinity(object): - def __repr__(self): # type: () -> str - return "Infinity" - - def __hash__(self): # type: () -> int - return hash(repr(self)) - - def __lt__(self, other): # type: (Any) -> bool - return False - - def __le__(self, other): # type: (Any) -> bool - return False - - def __eq__(self, other): # type: (Any) -> bool - return isinstance(other, self.__class__) - - def __ne__(self, other): # type: (Any) -> bool - return not isinstance(other, self.__class__) - - def __gt__(self, other): # type: (Any) -> bool - return True - - def __ge__(self, other): # type: (Any) -> bool - return True - - def __neg__(self): # type: () -> NegativeInfinity - return NegativeInfinity - - -Infinity = Infinity() # type: ignore - - -class NegativeInfinity(object): - def __repr__(self): # type: () -> str - return "-Infinity" - - def __hash__(self): # type: () -> int - return hash(repr(self)) - - def __lt__(self, other): # type: (Any) -> bool - return True - - def __le__(self, other): # type: (Any) -> bool - return True - - def __eq__(self, other): # type: (Any) -> bool - return isinstance(other, self.__class__) - - def __ne__(self, other): # type: (Any) -> bool - return not isinstance(other, self.__class__) - - def __gt__(self, other): # type: (Any) -> bool - return False - - def __ge__(self, other): # type: (Any) -> bool - return False - - def __neg__(self): # type: () -> Infinity - return Infinity - - -NegativeInfinity = NegativeInfinity() # type: ignore diff --git a/conda_lock/_vendor/poetry/core/version/version.py b/conda_lock/_vendor/poetry/core/version/version.py deleted file mode 100644 index 0726d9439..000000000 --- a/conda_lock/_vendor/poetry/core/version/version.py +++ /dev/null @@ -1,243 +0,0 @@ -import re - -from collections import namedtuple -from itertools import dropwhile -from typing import Any -from typing import Optional -from typing import Tuple -from typing import Union - -from .base import BaseVersion -from .exceptions import InvalidVersion -from .utils import Infinity -from .utils import NegativeInfinity - - -_Version = namedtuple("_Version", ["epoch", "release", "dev", "pre", "post", "local"]) - - -VERSION_PATTERN = re.compile( - r""" - ^ - v? - (?: - (?:(?P[0-9]+)!)? # epoch - (?P[0-9]+(?:\.[0-9]+)*) # release segment - (?P
                                          # pre-release
-            [-_.]?
-            (?P(a|b|c|rc|alpha|beta|pre|preview))
-            [-_.]?
-            (?P[0-9]+)?
-        )?
-        (?P                                         # post release
-            (?:-(?P[0-9]+))
-            |
-            (?:
-                [-_.]?
-                (?Ppost|rev|r)
-                [-_.]?
-                (?P[0-9]+)?
-            )
-        )?
-        (?P                                          # dev release
-            [-_.]?
-            (?Pdev)
-            [-_.]?
-            (?P[0-9]+)?
-        )?
-    )
-    (?:\+(?P[a-z0-9]+(?:[-_.][a-z0-9]+)*))?       # local version
-    $
-""",
-    re.IGNORECASE | re.VERBOSE,
-)
-
-
-class Version(BaseVersion):
-    def __init__(self, version):  # type: (str) -> None
-        # Validate the version and parse it into pieces
-        match = VERSION_PATTERN.match(version)
-        if not match:
-            raise InvalidVersion("Invalid version: '{0}'".format(version))
-
-        # Store the parsed out pieces of the version
-        self._version = _Version(
-            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
-            release=tuple(int(i) for i in match.group("release").split(".")),
-            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
-            post=_parse_letter_version(
-                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
-            ),
-            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
-            local=_parse_local_version(match.group("local")),
-        )
-
-        # Generate a key which will be used for sorting
-        self._key = _cmpkey(
-            self._version.epoch,
-            self._version.release,
-            self._version.pre,
-            self._version.post,
-            self._version.dev,
-            self._version.local,
-        )
-
-    def __repr__(self):  # type: () -> str
-        return "".format(repr(str(self)))
-
-    def __str__(self):  # type: () -> str
-        parts = []
-
-        # Epoch
-        if self._version.epoch != 0:
-            parts.append("{0}!".format(self._version.epoch))
-
-        # Release segment
-        parts.append(".".join(str(x) for x in self._version.release))
-
-        # Pre-release
-        if self._version.pre is not None:
-            parts.append("".join(str(x) for x in self._version.pre))
-
-        # Post-release
-        if self._version.post is not None:
-            parts.append(".post{0}".format(self._version.post[1]))
-
-        # Development release
-        if self._version.dev is not None:
-            parts.append(".dev{0}".format(self._version.dev[1]))
-
-        # Local version segment
-        if self._version.local is not None:
-            parts.append("+{0}".format(".".join(str(x) for x in self._version.local)))
-
-        return "".join(parts)
-
-    @property
-    def public(self):  # type: () -> str
-        return str(self).split("+", 1)[0]
-
-    @property
-    def base_version(self):  # type: () -> str
-        parts = []
-
-        # Epoch
-        if self._version.epoch != 0:
-            parts.append("{0}!".format(self._version.epoch))
-
-        # Release segment
-        parts.append(".".join(str(x) for x in self._version.release))
-
-        return "".join(parts)
-
-    @property
-    def local(self):  # type: () -> str
-        version_string = str(self)
-        if "+" in version_string:
-            return version_string.split("+", 1)[1]
-
-    @property
-    def is_prerelease(self):  # type: () -> bool
-        return bool(self._version.dev or self._version.pre)
-
-    @property
-    def is_postrelease(self):  # type: () -> bool
-        return bool(self._version.post)
-
-
-def _parse_letter_version(
-    letter, number
-):  # type: (str, Optional[str]) -> Tuple[str, int]
-    if letter:
-        # We consider there to be an implicit 0 in a pre-release if there is
-        # not a numeral associated with it.
-        if number is None:
-            number = 0
-
-        # We normalize any letters to their lower case form
-        letter = letter.lower()
-
-        # We consider some words to be alternate spellings of other words and
-        # in those cases we want to normalize the spellings to our preferred
-        # spelling.
-        if letter == "alpha":
-            letter = "a"
-        elif letter == "beta":
-            letter = "b"
-        elif letter in ["c", "pre", "preview"]:
-            letter = "rc"
-        elif letter in ["rev", "r"]:
-            letter = "post"
-
-        return letter, int(number)
-    if not letter and number:
-        # We assume if we are given a number, but we are not given a letter
-        # then this is using the implicit post release syntax (e.g. 1.0-1)
-        letter = "post"
-
-        return letter, int(number)
-
-
-_local_version_seperators = re.compile(r"[._-]")
-
-
-def _parse_local_version(local):  # type: (Optional[str]) -> Tuple[Union[str, int], ...]
-    """
-    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
-    """
-    if local is not None:
-        return tuple(
-            part.lower() if not part.isdigit() else int(part)
-            for part in _local_version_seperators.split(local)
-        )
-
-
-def _cmpkey(
-    epoch,  # type: int
-    release,  # type: Optional[Tuple[int, ...]]
-    pre,  # type: Optional[Tuple[str, int]]
-    post,  # type: Optional[Tuple[str, int]]
-    dev,  # type: Optional[Tuple[str, int]]
-    local,  # type: Optional[Tuple[Union[str, int], ...]]
-):  # type: (...) -> Tuple[int, Tuple[int, ...], Union[Union[Infinity, NegativeInfinity, Tuple[str, int]], Any], Union[NegativeInfinity, Tuple[str, int]], Union[Union[Infinity, Tuple[str, int]], Any], Union[NegativeInfinity, Tuple[Union[Tuple[int, str], Tuple[NegativeInfinity, Union[str, int]]], ...]]]
-    # When we compare a release version, we want to compare it with all of the
-    # trailing zeros removed. So we'll use a reverse the list, drop all the now
-    # leading zeros until we come to something non zero, then take the rest
-    # re-reverse it back into the correct order and make it a tuple and use
-    # that for our sorting key.
-    release = tuple(reversed(list(dropwhile(lambda x: x == 0, reversed(release)))))
-
-    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
-    # We'll do this by abusing the pre segment, but we _only_ want to do this
-    # if there is not a pre or a post segment. If we have one of those then
-    # the normal sorting rules will handle this case correctly.
-    if pre is None and post is None and dev is not None:
-        pre = -Infinity
-
-    # Versions without a pre-release (except as noted above) should sort after
-    # those with one.
-    elif pre is None:
-        pre = Infinity
-
-    # Versions without a post segment should sort before those with one.
-    if post is None:
-        post = -Infinity
-
-    # Versions without a development segment should sort after those with one.
-    if dev is None:
-        dev = Infinity
-
-    if local is None:
-        # Versions without a local segment should sort before those with one.
-        local = -Infinity
-    else:
-        # Versions with a local segment need that segment parsed to implement
-        # the sorting rules in PEP440.
-        # - Alpha numeric segments sort before numeric segments
-        # - Alpha numeric segments sort lexicographically
-        # - Numeric segments sort numerically
-        # - Shorter versions sort before longer versions when the prefixes
-        #   match exactly
-        local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
-
-    return epoch, release, pre, post, dev, local
diff --git a/conda_lock/_vendor/poetry/exceptions.py b/conda_lock/_vendor/poetry/exceptions.py
index 0bbaeb80a..0d7556675 100644
--- a/conda_lock/_vendor/poetry/exceptions.py
+++ b/conda_lock/_vendor/poetry/exceptions.py
@@ -1,8 +1,9 @@
-class PoetryException(Exception):
+from __future__ import annotations
+
 
+class PoetryException(Exception):
     pass
 
 
 class InvalidProjectFile(PoetryException):
-
     pass
diff --git a/conda_lock/_vendor/poetry/factory.py b/conda_lock/_vendor/poetry/factory.py
old mode 100755
new mode 100644
index 8c80d0ab1..4a7a1e692
--- a/conda_lock/_vendor/poetry/factory.py
+++ b/conda_lock/_vendor/poetry/factory.py
@@ -1,22 +1,39 @@
-from __future__ import absolute_import
-from __future__ import unicode_literals
+from __future__ import annotations
 
-from typing import Dict
-from typing import Optional
+import contextlib
+import logging
+import re
 
-from clikit.api.io.io import IO
+from typing import TYPE_CHECKING
+from typing import Any
+from typing import cast
 
+from conda_lock._vendor.cleo.io.null_io import NullIO
 from conda_lock._vendor.poetry.core.factory import Factory as BaseFactory
+from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP
+from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
 from conda_lock._vendor.poetry.core.toml.file import TOMLFile
 
-from .config.config import Config
-from .config.file_config_source import FileConfigSource
-from .io.null_io import NullIO
-from .locations import CONFIG_DIR
-from .packages.locker import Locker
-from .poetry import Poetry
-from .repositories.pypi_repository import PyPiRepository
-from .utils._compat import Path
+from conda_lock._vendor.poetry.config.config import Config
+from conda_lock._vendor.poetry.json import validate_object
+from conda_lock._vendor.poetry.packages.locker import Locker
+from conda_lock._vendor.poetry.plugins.plugin import Plugin
+from conda_lock._vendor.poetry.plugins.plugin_manager import PluginManager
+from conda_lock._vendor.poetry.poetry import Poetry
+
+
+if TYPE_CHECKING:
+    from pathlib import Path
+
+    from conda_lock._vendor.cleo.io.io import IO
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from tomlkit.toml_document import TOMLDocument
+
+    from conda_lock._vendor.poetry.repositories.legacy_repository import LegacyRepository
+    from conda_lock._vendor.poetry.utils.dependency_specification import DependencySpec
+
+
+logger = logging.getLogger(__name__)
 
 
 class Factory(BaseFactory):
@@ -25,27 +42,30 @@ class Factory(BaseFactory):
     """
 
     def create_poetry(
-        self, cwd=None, io=None
-    ):  # type: (Optional[Path], Optional[IO]) -> Poetry
+        self,
+        cwd: Path | None = None,
+        with_groups: bool = True,
+        io: IO | None = None,
+        disable_plugins: bool = False,
+        disable_cache: bool = False,
+    ) -> Poetry:
         if io is None:
             io = NullIO()
 
-        base_poetry = super(Factory, self).create_poetry(cwd)
+        base_poetry = super().create_poetry(cwd=cwd, with_groups=with_groups)
 
         locker = Locker(
             base_poetry.file.parent / "poetry.lock", base_poetry.local_config
         )
 
         # Loading global configuration
-        config = self.create_config(io)
+        config = Config.create()
 
         # Loading local configuration
         local_config_file = TOMLFile(base_poetry.file.parent / "poetry.toml")
         if local_config_file.exists():
             if io.is_debug():
-                io.write_line(
-                    "Loading configuration file {}".format(local_config_file.path)
-                )
+                io.write_line(f"Loading configuration file {local_config_file.path}")
 
             config.merge(local_config_file.read())
 
@@ -55,9 +75,8 @@ def create_poetry(
         for source in base_poetry.pyproject.poetry_config.get("source", []):
             name = source.get("name")
             url = source.get("url")
-            if name and url:
-                if name not in existing_repositories:
-                    repositories[name] = {"url": url}
+            if name and url and name not in existing_repositories:
+                repositories[name] = {"url": url}
 
         config.merge({"repositories": repositories})
 
@@ -67,18 +86,49 @@ def create_poetry(
             base_poetry.package,
             locker,
             config,
+            disable_cache,
         )
 
         # Configuring sources
-        sources = poetry.local_config.get("source", [])
+        self.configure_sources(
+            poetry,
+            poetry.local_config.get("source", []),
+            config,
+            io,
+            disable_cache=disable_cache,
+        )
+
+        plugin_manager = PluginManager(Plugin.group, disable_plugins=disable_plugins)
+        plugin_manager.load_plugins()
+        poetry.set_plugin_manager(plugin_manager)
+        plugin_manager.activate(poetry, io)
+
+        return poetry
+
+    @classmethod
+    def get_package(cls, name: str, version: str) -> ProjectPackage:
+        return ProjectPackage(name, version, version)
+
+    @classmethod
+    def configure_sources(
+        cls,
+        poetry: Poetry,
+        sources: list[dict[str, str]],
+        config: Config,
+        io: IO,
+        disable_cache: bool = False,
+    ) -> None:
+        if disable_cache:
+            logger.debug("Disabling source caches")
+
         for source in sources:
-            repository = self.create_legacy_repository(source, config)
-            is_default = source.get("default", False)
-            is_secondary = source.get("secondary", False)
+            repository = cls.create_package_source(
+                source, config, disable_cache=disable_cache
+            )
+            is_default = bool(source.get("default", False))
+            is_secondary = bool(source.get("secondary", False))
             if io.is_debug():
-                message = "Adding repository {} ({})".format(
-                    repository.name, repository.url
-                )
+                message = f"Adding repository {repository.name} ({repository.url})"
                 if is_default:
                     message += " and setting it as the default one"
                 elif is_secondary:
@@ -95,68 +145,157 @@ def create_poetry(
             if io.is_debug():
                 io.write_line("Deactivating the PyPI repository")
         else:
+            from conda_lock._vendor.poetry.repositories.pypi_repository import PyPiRepository
+
             default = not poetry.pool.has_primary_repositories()
-            poetry.pool.add_repository(PyPiRepository(), default, not default)
+            poetry.pool.add_repository(
+                PyPiRepository(disable_cache=disable_cache), default, not default
+            )
 
-        return poetry
+    @classmethod
+    def create_package_source(
+        cls, source: dict[str, str], auth_config: Config, disable_cache: bool = False
+    ) -> LegacyRepository:
+        from conda_lock._vendor.poetry.repositories.legacy_repository import LegacyRepository
+        from conda_lock._vendor.poetry.repositories.single_page_repository import SinglePageRepository
+
+        if "url" not in source:
+            raise RuntimeError("Unsupported source specified")
+
+        # PyPI-like repository
+        if "name" not in source:
+            raise RuntimeError("Missing [name] in source.")
+        name = source["name"]
+        url = source["url"]
+
+        repository_class = LegacyRepository
+
+        if re.match(r".*\.(htm|html)$", url):
+            repository_class = SinglePageRepository
+
+        return repository_class(
+            name,
+            url,
+            config=auth_config,
+            disable_cache=disable_cache,
+        )
 
     @classmethod
-    def create_config(cls, io=None):  # type: (Optional[IO]) -> Config
-        if io is None:
-            io = NullIO()
+    def create_pyproject_from_package(
+        cls, package: Package, path: Path | None = None
+    ) -> TOMLDocument:
+        import tomlkit
 
-        config = Config()
-        # Load global config
-        config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml")
-        if config_file.exists():
-            if io.is_debug():
-                io.write_line(
-                    "Loading configuration file {}".format(
-                        config_file.path
-                    )
-                )
+        from conda_lock._vendor.poetry.utils.dependency_specification import dependency_to_specification
 
-            config.merge(config_file.read())
+        pyproject: dict[str, Any] = tomlkit.document()
 
-        config.set_config_source(FileConfigSource(config_file))
+        pyproject["tool"] = tomlkit.table(is_super_table=True)
 
-        # Load global auth config
-        auth_config_file = TOMLFile(Path(CONFIG_DIR) / "auth.toml")
-        if auth_config_file.exists():
-            if io.is_debug():
-                io.write_line(
-                    "Loading configuration file {}".format(
-                        auth_config_file.path
-                    )
-                )
+        content: dict[str, Any] = tomlkit.table()
+        pyproject["tool"]["poetry"] = content
 
-            config.merge(auth_config_file.read())
+        content["name"] = package.name
+        content["version"] = package.version.text
+        content["description"] = package.description
+        content["authors"] = package.authors
+        content["license"] = package.license.id if package.license else ""
 
-        config.set_auth_config_source(FileConfigSource(auth_config_file))
+        if package.classifiers:
+            content["classifiers"] = package.classifiers
 
-        return config
+        for key, attr in {
+            ("documentation", "documentation_url"),
+            ("repository", "repository_url"),
+            ("homepage", "homepage"),
+            ("maintainers", "maintainers"),
+            ("keywords", "keywords"),
+        }:
+            value = getattr(package, attr, None)
+            if value:
+                content[key] = value
 
-    def create_legacy_repository(
-        self, source, auth_config
-    ):  # type: (Dict[str, str], Config) -> LegacyRepository
-        from .repositories.legacy_repository import LegacyRepository
-        from .utils.helpers import get_cert
-        from .utils.helpers import get_client_cert
+        readmes = []
 
-        if "url" in source:
-            # PyPI-like repository
-            if "name" not in source:
-                raise RuntimeError("Missing [name] in source.")
-        else:
-            raise RuntimeError("Unsupported source specified")
+        for readme in package.readmes:
+            readme_posix_path = readme.as_posix()
 
-        name = source["name"]
-        url = source["url"]
+            with contextlib.suppress(ValueError):
+                if package.root_dir:
+                    readme_posix_path = readme.relative_to(package.root_dir).as_posix()
 
-        return LegacyRepository(
-            name,
-            url,
-            config=auth_config,
-            cert=get_cert(auth_config, name),
-            client_cert=get_client_cert(auth_config, name),
-        )
+            readmes.append(readme_posix_path)
+
+        if readmes:
+            content["readme"] = readmes
+
+        optional_dependencies = set()
+        extras_section = None
+
+        if package.extras:
+            extras_section = tomlkit.table()
+
+            for extra in package.extras:
+                _dependencies = []
+                for dependency in package.extras[extra]:
+                    _dependencies.append(dependency.name)
+                    optional_dependencies.add(dependency.name)
+
+                extras_section[extra] = _dependencies
+
+        optional_dependencies = set(optional_dependencies)
+        dependency_section = content["dependencies"] = tomlkit.table()
+        dependency_section["python"] = package.python_versions
+
+        for dep in package.all_requires:
+            constraint: DependencySpec | str = dependency_to_specification(
+                dep, tomlkit.inline_table()
+            )
+
+            if not isinstance(constraint, str):
+                if dep.name in optional_dependencies:
+                    constraint["optional"] = True
+
+                if len(constraint) == 1 and "version" in constraint:
+                    assert isinstance(constraint["version"], str)
+                    constraint = constraint["version"]
+                elif not constraint:
+                    constraint = "*"
+
+            for group in dep.groups:
+                if group == MAIN_GROUP:
+                    dependency_section[dep.name] = constraint
+                else:
+                    if "group" not in content:
+                        content["group"] = tomlkit.table(is_super_table=True)
+
+                    if group not in content["group"]:
+                        content["group"][group] = tomlkit.table(is_super_table=True)
+
+                    if "dependencies" not in content["group"][group]:
+                        content["group"][group]["dependencies"] = tomlkit.table()
+
+                    content["group"][group]["dependencies"][dep.name] = constraint
+
+        if extras_section:
+            content["extras"] = extras_section
+
+        pyproject = cast("TOMLDocument", pyproject)
+        pyproject.add(tomlkit.nl())
+
+        if path:
+            path.joinpath("pyproject.toml").write_text(
+                pyproject.as_string(), encoding="utf-8"
+            )
+
+        return pyproject
+
+    @classmethod
+    def validate(
+        cls, config: dict[str, Any], strict: bool = False
+    ) -> dict[str, list[str]]:
+        results = super().validate(config, strict)
+
+        results["errors"].extend(validate_object(config))
+
+        return results
diff --git a/conda_lock/_vendor/poetry/inspection/info.py b/conda_lock/_vendor/poetry/inspection/info.py
index 78e5f40d9..214485acb 100644
--- a/conda_lock/_vendor/poetry/inspection/info.py
+++ b/conda_lock/_vendor/poetry/inspection/info.py
@@ -1,71 +1,85 @@
+from __future__ import annotations
+
+import contextlib
+import functools
 import glob
 import logging
 import os
 import tarfile
 import zipfile
 
-from typing import Dict
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Union
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Any
 
 import pkginfo
 
 from conda_lock._vendor.poetry.core.factory import Factory
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.packages import ProjectPackage
-from conda_lock._vendor.poetry.core.packages import dependency_from_pep_508
+from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+from conda_lock._vendor.poetry.core.packages.package import Package
 from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML
-from conda_lock._vendor.poetry.core.utils._compat import PY35
-from conda_lock._vendor.poetry.core.utils._compat import Path
 from conda_lock._vendor.poetry.core.utils.helpers import parse_requires
 from conda_lock._vendor.poetry.core.utils.helpers import temporary_directory
 from conda_lock._vendor.poetry.core.version.markers import InvalidMarker
+
 from conda_lock._vendor.poetry.utils.env import EnvCommandError
-from conda_lock._vendor.poetry.utils.env import EnvManager
-from conda_lock._vendor.poetry.utils.env import VirtualEnv
+from conda_lock._vendor.poetry.utils.env import ephemeral_environment
 from conda_lock._vendor.poetry.utils.setup_reader import SetupReader
 
 
+if TYPE_CHECKING:
+    from collections.abc import Callable
+    from collections.abc import Iterator
+    from contextlib import AbstractContextManager
+
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
+
+
 logger = logging.getLogger(__name__)
 
 PEP517_META_BUILD = """\
-import pep517.build
-import pep517.meta
-
-path='{source}'
-system=pep517.build.compat_system(path)
-pep517.meta.build(source_dir=path, dest='{dest}', system=system)
+import build
+import build.env
+import pep517
+
+source = '{source}'
+dest = '{dest}'
+
+with build.env.IsolatedEnvBuilder() as env:
+    builder = build.ProjectBuilder(
+        srcdir=source,
+        scripts_dir=env.scripts_dir,
+        python_executable=env.executable,
+        runner=pep517.quiet_subprocess_runner,
+    )
+    env.install(builder.build_system_requires)
+    env.install(builder.get_requires_for_build('wheel'))
+    builder.metadata_path(dest)
 """
 
-PEP517_META_BUILD_DEPS = ["pep517===0.8.2", "toml==0.10.1"]
+PEP517_META_BUILD_DEPS = ["build===0.7.0", "pep517==0.12.0"]
 
 
 class PackageInfoError(ValueError):
-    def __init__(
-        self, path, *reasons
-    ):  # type: (Union[Path, str], *Union[BaseException, str]) -> None
-        reasons = (
-            "Unable to determine package info for path: {}".format(str(path)),
-        ) + reasons
-        super(PackageInfoError, self).__init__(
-            "\n\n".join(str(msg).strip() for msg in reasons if msg)
-        )
+    def __init__(self, path: Path | str, *reasons: BaseException | str) -> None:
+        reasons = (f"Unable to determine package info for path: {path!s}",) + reasons
+        super().__init__("\n\n".join(str(msg).strip() for msg in reasons if msg))
 
 
 class PackageInfo:
     def __init__(
         self,
-        name=None,  # type: Optional[str]
-        version=None,  # type: Optional[str]
-        summary=None,  # type: Optional[str]
-        platform=None,  # type: Optional[str]
-        requires_dist=None,  # type: Optional[List[str]]
-        requires_python=None,  # type: Optional[str]
-        files=None,  # type: Optional[List[str]]
-        cache_version=None,  # type: Optional[str]
-    ):
+        *,
+        name: str | None = None,
+        version: str | None = None,
+        summary: str | None = None,
+        platform: str | None = None,
+        requires_dist: list[str] | None = None,
+        requires_python: str | None = None,
+        files: list[dict[str, str]] | None = None,
+        yanked: str | bool = False,
+        cache_version: str | None = None,
+    ) -> None:
         self.name = name
         self.version = version
         self.summary = summary
@@ -73,16 +87,17 @@ def __init__(
         self.requires_dist = requires_dist
         self.requires_python = requires_python
         self.files = files or []
+        self.yanked = yanked
         self._cache_version = cache_version
-        self._source_type = None
-        self._source_url = None
-        self._source_reference = None
+        self._source_type: str | None = None
+        self._source_url: str | None = None
+        self._source_reference: str | None = None
 
     @property
-    def cache_version(self):  # type: () -> Optional[str]
+    def cache_version(self) -> str | None:
         return self._cache_version
 
-    def update(self, other):  # type: (PackageInfo) -> PackageInfo
+    def update(self, other: PackageInfo) -> PackageInfo:
         self.name = other.name or self.name
         self.version = other.version or self.version
         self.summary = other.summary or self.summary
@@ -93,7 +108,7 @@ def update(self, other):  # type: (PackageInfo) -> PackageInfo
         self._cache_version = other.cache_version or self._cache_version
         return self
 
-    def asdict(self):  # type: () -> Dict[str, Optional[Union[str, List[str]]]]
+    def asdict(self) -> dict[str, Any]:
         """
         Helper method to convert package info into a dictionary used for caching.
         """
@@ -105,44 +120,46 @@ def asdict(self):  # type: () -> Dict[str, Optional[Union[str, List[str]]]]
             "requires_dist": self.requires_dist,
             "requires_python": self.requires_python,
             "files": self.files,
+            "yanked": self.yanked,
             "_cache_version": self._cache_version,
         }
 
     @classmethod
-    def load(
-        cls, data
-    ):  # type: (Dict[str, Optional[Union[str, List[str]]]]) -> PackageInfo
+    def load(cls, data: dict[str, Any]) -> PackageInfo:
         """
         Helper method to load data from a dictionary produced by `PackageInfo.asdict()`.
 
-        :param data: Data to load. This is expected to be a `dict` object output by `asdict()`.
+        :param data: Data to load. This is expected to be a `dict` object output by
+            `asdict()`.
         """
         cache_version = data.pop("_cache_version", None)
         return cls(cache_version=cache_version, **data)
 
-    @classmethod
-    def _log(cls, msg, level="info"):
-        """Internal helper method to log information."""
-        getattr(logger, level)("{}: {}".format(cls.__name__, msg))
-
     def to_package(
-        self, name=None, extras=None, root_dir=None
-    ):  # type: (Optional[str], Optional[List[str]], Optional[Path]) -> Package
+        self,
+        name: str | None = None,
+        extras: list[str] | None = None,
+        root_dir: Path | None = None,
+    ) -> Package:
         """
-        Create a new `poetry.core.packages.package.Package` instance using metadata from this instance.
+        Create a new `poetry.core.packages.package.Package` instance using metadata from
+        this instance.
 
-        :param name: Name to use for the package, if not specified name from this instance is used.
+        :param name: Name to use for the package, if not specified name from this
+            instance is used.
         :param extras: Extras to activate for this package.
-        :param root_dir:  Optional root directory to use for the package. If set, dependency strings
-            will be parsed relative to this directory.
+        :param root_dir:  Optional root directory to use for the package. If set,
+            dependency strings will be parsed relative to this directory.
         """
         name = name or self.name
 
+        if not name:
+            raise RuntimeError("Unable to create package with no name")
+
         if not self.version:
-            # The version could not be determined, so we raise an error since it is mandatory.
-            raise RuntimeError(
-                "Unable to retrieve the package version for {}".format(name)
-            )
+            # The version could not be determined, so we raise an error since it is
+            # mandatory.
+            raise RuntimeError(f"Unable to retrieve the package version for {name}")
 
         package = Package(
             name=name,
@@ -150,19 +167,30 @@ def to_package(
             source_type=self._source_type,
             source_url=self._source_url,
             source_reference=self._source_reference,
+            yanked=self.yanked,
         )
-        package.description = self.summary
+        if self.summary is not None:
+            package.description = self.summary
         package.root_dir = root_dir
         package.python_versions = self.requires_python or "*"
         package.files = self.files
 
-        if root_dir or (self._source_type in {"directory"} and self._source_url):
-            # this is a local poetry project, this means we can extract "richer" requirement information
-            # eg: development requirements etc.
-            poetry_package = self._get_poetry_package(path=root_dir or self._source_url)
+        # If this is a local poetry project, we can extract "richer" requirement
+        # information, eg: development requirements etc.
+        if root_dir is not None:
+            path = root_dir
+        elif self._source_type == "directory" and self._source_url is not None:
+            path = Path(self._source_url)
+        else:
+            path = None
+
+        if path is not None:
+            poetry_package = self._get_poetry_package(path=path)
             if poetry_package:
                 package.extras = poetry_package.extras
-                package.requires = poetry_package.requires
+                for dependency in poetry_package.requires:
+                    package.add_dependency(dependency)
+
                 return package
 
         seen_requirements = set()
@@ -170,17 +198,18 @@ def to_package(
         for req in self.requires_dist or []:
             try:
                 # Attempt to parse the PEP-508 requirement string
-                dependency = dependency_from_pep_508(req, relative_to=root_dir)
+                dependency = Dependency.create_from_pep_508(req, relative_to=root_dir)
             except InvalidMarker:
                 # Invalid marker, We strip the markers hoping for the best
                 req = req.split(";")[0]
-                dependency = dependency_from_pep_508(req, relative_to=root_dir)
+                dependency = Dependency.create_from_pep_508(req, relative_to=root_dir)
             except ValueError:
                 # Likely unable to parse constraint so we skip it
-                self._log(
-                    "Invalid constraint ({}) found in {}-{} dependencies, "
-                    "skipping".format(req, package.name, package.version),
-                    level="warning",
+                logger.warning(
+                    "Invalid constraint (%s) found in %s-%s dependencies, skipping",
+                    req,
+                    package.name,
+                    package.version,
                 )
                 continue
 
@@ -188,7 +217,8 @@ def to_package(
                 # this dependency is required by an extra package
                 for extra in dependency.in_extras:
                     if extra not in package.extras:
-                        # this is the first time we encounter this extra for this package
+                        # this is the first time we encounter this extra for this
+                        # package
                         package.extras[extra] = []
 
                     package.extras[extra].append(dependency)
@@ -196,17 +226,18 @@ def to_package(
             req = dependency.to_pep_508(with_extras=True)
 
             if req not in seen_requirements:
-                package.requires.append(dependency)
+                package.add_dependency(dependency)
                 seen_requirements.add(req)
 
         return package
 
     @classmethod
     def _from_distribution(
-        cls, dist
-    ):  # type: (Union[pkginfo.BDist, pkginfo.SDist, pkginfo.Wheel]) -> PackageInfo
+        cls, dist: pkginfo.BDist | pkginfo.SDist | pkginfo.Wheel
+    ) -> PackageInfo:
         """
-        Helper method to parse package information from a `pkginfo.Distribution` instance.
+        Helper method to parse package information from a `pkginfo.Distribution`
+        instance.
 
         :param dist: The distribution instance to parse information from.
         """
@@ -235,11 +266,11 @@ def _from_distribution(
         return info
 
     @classmethod
-    def _from_sdist_file(cls, path):  # type: (Path) -> PackageInfo
+    def _from_sdist_file(cls, path: Path) -> PackageInfo:
         """
-        Helper method to parse package information from an sdist file. We attempt to first inspect the
-        file using `pkginfo.SDist`. If this does not provide us with package requirements, we extract the
-        source and handle it as a directory.
+        Helper method to parse package information from an sdist file. We attempt to
+        first inspect the file using `pkginfo.SDist`. If this does not provide us with
+        package requirements, we extract the source and handle it as a directory.
 
         :param path: The sdist file to parse information from.
         """
@@ -260,6 +291,9 @@ def _from_sdist_file(cls, path):  # type: (Path) -> PackageInfo
         # So, we unpack and introspect
         suffix = path.suffix
 
+        context: Callable[
+            [str], AbstractContextManager[zipfile.ZipFile | tarfile.TarFile]
+        ]
         if suffix == ".zip":
             context = zipfile.ZipFile
         else:
@@ -272,8 +306,8 @@ def _from_sdist_file(cls, path):  # type: (Path) -> PackageInfo
 
             context = tarfile.open
 
-        with temporary_directory() as tmp:
-            tmp = Path(tmp)
+        with temporary_directory() as tmp_str:
+            tmp = Path(tmp_str)
             with context(path.as_posix()) as archive:
                 archive.extractall(tmp.as_posix())
 
@@ -296,15 +330,16 @@ def _from_sdist_file(cls, path):  # type: (Path) -> PackageInfo
         return info.update(new_info)
 
     @staticmethod
-    def has_setup_files(path):  # type: (Path) -> bool
+    def has_setup_files(path: Path) -> bool:
         return any((path / f).exists() for f in SetupReader.FILES)
 
     @classmethod
-    def from_setup_files(cls, path):  # type: (Path) -> PackageInfo
+    def from_setup_files(cls, path: Path) -> PackageInfo:
         """
-        Mechanism to parse package information from a `setup.[py|cfg]` file. This uses the implementation
-        at `poetry.utils.setup_reader.SetupReader` in order to parse the file. This is not reliable for
-        complex setup files and should only attempted as a fallback.
+        Mechanism to parse package information from a `setup.[py|cfg]` file. This uses
+        the implementation at `poetry.utils.setup_reader.SetupReader` in order to parse
+        the file. This is not reliable for complex setup files and should only attempted
+        as a fallback.
 
         :param path: Path to `setup.py` file
         """
@@ -322,15 +357,12 @@ def from_setup_files(cls, path):  # type: (Path) -> PackageInfo
         if python_requires is None:
             python_requires = "*"
 
-        requires = ""
-        for dep in result["install_requires"]:
-            requires += dep + "\n"
-
+        requires = "".join(dep + "\n" for dep in result["install_requires"])
         if result["extras_require"]:
             requires += "\n"
 
         for extra_name, deps in result["extras_require"].items():
-            requires += "[{}]\n".format(extra_name)
+            requires += f"[{extra_name}]\n"
 
             for dep in deps:
                 requires += dep + "\n"
@@ -357,26 +389,23 @@ def from_setup_files(cls, path):  # type: (Path) -> PackageInfo
         return info
 
     @staticmethod
-    def _find_dist_info(path):  # type: (Path) -> Iterator[Path]
+    def _find_dist_info(path: Path) -> Iterator[Path]:
         """
         Discover all `*.*-info` directories in a given path.
 
         :param path: Path to search.
         """
         pattern = "**/*.*-info"
-        if PY35:
-            # Sometimes pathlib will fail on recursive symbolic links, so we need to workaround it
-            # and use the glob module instead. Note that this does not happen with pathlib2
-            # so it's safe to use it for Python < 3.4.
-            directories = glob.iglob(path.joinpath(pattern).as_posix(), recursive=True)
-        else:
-            directories = path.glob(pattern)
+        # Sometimes pathlib will fail on recursive symbolic links, so we need to work
+        # around it and use the glob module instead. Note that this does not happen with
+        # pathlib2 so it's safe to use it for Python < 3.4.
+        directories = glob.iglob(path.joinpath(pattern).as_posix(), recursive=True)
 
         for d in directories:
             yield Path(d)
 
     @classmethod
-    def from_metadata(cls, path):  # type: (Path) -> Optional[PackageInfo]
+    def from_metadata(cls, path: Path) -> PackageInfo | None:
         """
         Helper method to parse package information from an unpacked metadata directory.
 
@@ -385,7 +414,7 @@ def from_metadata(cls, path):  # type: (Path) -> Optional[PackageInfo]
         if path.suffix in {".dist-info", ".egg-info"}:
             directories = [path]
         else:
-            directories = cls._find_dist_info(path=path)
+            directories = list(cls._find_dist_info(path=path))
 
         for directory in directories:
             try:
@@ -403,14 +432,12 @@ def from_metadata(cls, path):  # type: (Path) -> Optional[PackageInfo]
                 # handle PKG-INFO in unpacked sdist root
                 dist = pkginfo.UnpackedSDist(path.as_posix())
             except ValueError:
-                return
+                return None
 
-        info = cls._from_distribution(dist=dist)
-        if info:
-            return info
+        return cls._from_distribution(dist=dist)
 
     @classmethod
-    def from_package(cls, package):  # type: (Package) -> PackageInfo
+    def from_package(cls, package: Package) -> PackageInfo:
         """
         Helper method to inspect a `Package` object, in order to generate package info.
 
@@ -430,107 +457,33 @@ def from_package(cls, package):  # type: (Package) -> PackageInfo
             requires_dist=list(requires),
             requires_python=package.python_versions,
             files=package.files,
+            yanked=package.yanked_reason if package.yanked else False,
         )
 
     @staticmethod
-    def _get_poetry_package(path):  # type: (Path) -> Optional[ProjectPackage]
+    def _get_poetry_package(path: Path) -> ProjectPackage | None:
         # Note: we ignore any setup.py file at this step
         # TODO: add support for handling non-poetry PEP-517 builds
         if PyProjectTOML(path.joinpath("pyproject.toml")).is_poetry_project():
-            try:
+            with contextlib.suppress(RuntimeError):
                 return Factory().create_poetry(path).package
-            except RuntimeError:
-                return None
 
         return None
 
     @classmethod
-    def _pep517_metadata(cls, path):  # type (Path) -> PackageInfo
-        """
-        Helper method to use PEP-517 library to build and read package metadata.
-
-        :param path: Path to package source to build and read metadata for.
+    def from_directory(cls, path: Path, disable_build: bool = False) -> PackageInfo:
         """
-        info = None
-        try:
-            info = cls.from_setup_files(path)
-            if all([info.version, info.name, info.requires_dist]):
-                return info
-        except PackageInfoError:
-            pass
-
-        with temporary_directory() as tmp_dir:
-            # TODO: cache PEP 517 build environment corresponding to each project venv
-            venv_dir = Path(tmp_dir) / ".venv"
-            EnvManager.build_venv(venv_dir.as_posix())
-            venv = VirtualEnv(venv_dir, venv_dir)
-
-            dest_dir = Path(tmp_dir) / "dist"
-            dest_dir.mkdir()
-
-            try:
-                venv.run_python(
-                    "-m",
-                    "pip",
-                    "install",
-                    "--disable-pip-version-check",
-                    "--ignore-installed",
-                    *PEP517_META_BUILD_DEPS
-                )
-                venv.run_python(
-                    "-",
-                    input_=PEP517_META_BUILD.format(
-                        source=path.as_posix(), dest=dest_dir.as_posix()
-                    ),
-                )
-                return cls.from_metadata(dest_dir)
-            except EnvCommandError as e:
-                # something went wrong while attempting pep517 metadata build
-                # fallback to egg_info if setup.py available
-                cls._log("PEP517 build failed: {}".format(e), level="debug")
-                setup_py = path / "setup.py"
-                if not setup_py.exists():
-                    raise PackageInfoError(
-                        path,
-                        e,
-                        "No fallback setup.py file was found to generate egg_info.",
-                    )
-
-                cwd = Path.cwd()
-                os.chdir(path.as_posix())
-                try:
-                    venv.run_python("setup.py", "egg_info")
-                    return cls.from_metadata(path)
-                except EnvCommandError as fbe:
-                    raise PackageInfoError(
-                        path, "Fallback egg_info generation failed.", fbe
-                    )
-                finally:
-                    os.chdir(cwd.as_posix())
-
-        if info:
-            cls._log(
-                "Falling back to parsed setup.py file for {}".format(path), "debug"
-            )
-            return info
-
-        # if we reach here, everything has failed and all hope is lost
-        raise PackageInfoError(path, "Exhausted all core metadata sources.")
-
-    @classmethod
-    def from_directory(
-        cls, path, disable_build=False
-    ):  # type: (Path, bool) -> PackageInfo
-        """
-        Generate package information from a package source directory. If `disable_build` is not `True` and
-        introspection of all available metadata fails, the package is attempted to be build in an isolated
-        environment so as to generate required metadata.
+        Generate package information from a package source directory. If `disable_build`
+        is not `True` and introspection of all available metadata fails, the package is
+        attempted to be built in an isolated environment so as to generate required
+        metadata.
 
         :param path: Path to generate package information from.
-        :param disable_build: If not `True` and setup reader fails, PEP 517 isolated build is attempted in
-            order to gather metadata.
+        :param disable_build: If not `True` and setup reader fails, PEP 517 isolated
+            build is attempted in order to gather metadata.
         """
         project_package = cls._get_poetry_package(path)
+        info: PackageInfo | None
         if project_package:
             info = cls.from_package(project_package)
         else:
@@ -541,7 +494,7 @@ def from_directory(
                     if disable_build:
                         info = cls.from_setup_files(path)
                     else:
-                        info = cls._pep517_metadata(path)
+                        info = get_pep517_metadata(path)
                 except PackageInfoError:
                     if not info:
                         raise
@@ -554,7 +507,7 @@ def from_directory(
         return info
 
     @classmethod
-    def from_sdist(cls, path):  # type: (Path) -> PackageInfo
+    def from_sdist(cls, path: Path) -> PackageInfo:
         """
         Gather package information from an sdist file, packed or unpacked.
 
@@ -568,7 +521,7 @@ def from_sdist(cls, path):  # type: (Path) -> PackageInfo
         return cls.from_directory(path=path)
 
     @classmethod
-    def from_wheel(cls, path):  # type: (Path) -> PackageInfo
+    def from_wheel(cls, path: Path) -> PackageInfo:
         """
         Gather package information from a wheel.
 
@@ -580,7 +533,7 @@ def from_wheel(cls, path):  # type: (Path) -> PackageInfo
             return PackageInfo()
 
     @classmethod
-    def from_bdist(cls, path):  # type: (Path) -> PackageInfo
+    def from_bdist(cls, path: Path) -> PackageInfo:
         """
         Gather package information from a bdist (wheel etc.).
 
@@ -598,7 +551,7 @@ def from_bdist(cls, path):  # type: (Path) -> PackageInfo
             raise PackageInfoError(path, e)
 
     @classmethod
-    def from_path(cls, path):  # type: (Path) -> PackageInfo
+    def from_path(cls, path: Path) -> PackageInfo:
         """
         Gather package information from a given path (bdist, sdist, directory).
 
@@ -608,3 +561,74 @@ def from_path(cls, path):  # type: (Path) -> PackageInfo
             return cls.from_bdist(path=path)
         except PackageInfoError:
             return cls.from_sdist(path=path)
+
+
+@functools.lru_cache(maxsize=None)
+def get_pep517_metadata(path: Path) -> PackageInfo:
+    """
+    Helper method to use PEP-517 library to build and read package metadata.
+
+    :param path: Path to package source to build and read metadata for.
+    """
+    info = None
+
+    with contextlib.suppress(PackageInfoError):
+        info = PackageInfo.from_setup_files(path)
+        if all([info.version, info.name, info.requires_dist]):
+            return info
+
+    with ephemeral_environment(
+        flags={"no-pip": False, "no-setuptools": False, "no-wheel": False}
+    ) as venv:
+        # TODO: cache PEP 517 build environment corresponding to each project venv
+        dest_dir = venv.path.parent / "dist"
+        dest_dir.mkdir()
+
+        pep517_meta_build_script = PEP517_META_BUILD.format(
+            source=path.as_posix(), dest=dest_dir.as_posix()
+        )
+
+        try:
+            venv.run_pip(
+                "install",
+                "--disable-pip-version-check",
+                "--ignore-installed",
+                "--no-input",
+                *PEP517_META_BUILD_DEPS,
+            )
+            venv.run(
+                "python",
+                "-",
+                input_=pep517_meta_build_script,
+            )
+            info = PackageInfo.from_metadata(dest_dir)
+        except EnvCommandError as e:
+            # something went wrong while attempting pep517 metadata build
+            # fallback to egg_info if setup.py available
+            logger.debug("PEP517 build failed: %s", e)
+            setup_py = path / "setup.py"
+            if not setup_py.exists():
+                raise PackageInfoError(
+                    path,
+                    e,
+                    "No fallback setup.py file was found to generate egg_info.",
+                )
+
+            cwd = Path.cwd()
+            os.chdir(path.as_posix())
+            try:
+                venv.run("python", "setup.py", "egg_info")
+                info = PackageInfo.from_metadata(path)
+            except EnvCommandError as fbe:
+                raise PackageInfoError(
+                    path, "Fallback egg_info generation failed.", fbe
+                )
+            finally:
+                os.chdir(cwd.as_posix())
+
+    if info:
+        logger.debug("Falling back to parsed setup.py file for %s", path)
+        return info
+
+    # if we reach here, everything has failed and all hope is lost
+    raise PackageInfoError(path, "Exhausted all core metadata sources.")
diff --git a/conda_lock/_vendor/poetry/installation/__init__.py b/conda_lock/_vendor/poetry/installation/__init__.py
index 385d7b8ce..bb543a4df 100644
--- a/conda_lock/_vendor/poetry/installation/__init__.py
+++ b/conda_lock/_vendor/poetry/installation/__init__.py
@@ -1 +1,6 @@
-from .installer import Installer
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.installation.installer import Installer
+
+
+__all__ = ["Installer"]
diff --git a/conda_lock/_vendor/poetry/installation/authenticator.py b/conda_lock/_vendor/poetry/installation/authenticator.py
deleted file mode 100644
index 58d0314e5..000000000
--- a/conda_lock/_vendor/poetry/installation/authenticator.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import logging
-import time
-
-from typing import TYPE_CHECKING
-
-import requests
-import requests.auth
-import requests.exceptions
-
-from conda_lock._vendor.poetry.exceptions import PoetryException
-from conda_lock._vendor.poetry.utils._compat import urlparse
-from conda_lock._vendor.poetry.utils.password_manager import PasswordManager
-
-
-if TYPE_CHECKING:
-    from typing import Any
-    from typing import Optional
-    from typing import Tuple
-
-    from clikit.api.io import IO
-
-    from conda_lock._vendor.poetry.config.config import Config
-
-
-logger = logging.getLogger()
-
-
-class Authenticator(object):
-    def __init__(self, config, io=None):  # type: (Config, Optional[IO]) -> None
-        self._config = config
-        self._io = io
-        self._credentials = {}
-        self._password_manager = PasswordManager(self._config)
-
-    def _log(self, message, level="debug"):  # type: (str, str) -> None
-        if self._io is not None:
-            self._io.write_line(
-                "<{level:s}>{message:s}".format(
-                    message=message, level=level
-                )
-            )
-        else:
-            getattr(logger, level, logger.debug)(message)
-
-    @property
-    def session(self):  # type: () -> requests.Session
-        return requests.Session()
-
-    def request(
-        self, method, url, **kwargs
-    ):  # type: (str, str, Any) -> requests.Response
-        request = requests.Request(method, url)
-        username, password = self.get_credentials_for_url(url)
-
-        if username is not None and password is not None:
-            request = requests.auth.HTTPBasicAuth(username, password)(request)
-
-        session = self.session
-        prepared_request = session.prepare_request(request)
-
-        proxies = kwargs.get("proxies", {})
-        stream = kwargs.get("stream")
-        verify = kwargs.get("verify")
-        cert = kwargs.get("cert")
-
-        settings = session.merge_environment_settings(
-            prepared_request.url, proxies, stream, verify, cert
-        )
-
-        # Send the request.
-        send_kwargs = {
-            "timeout": kwargs.get("timeout"),
-            "allow_redirects": kwargs.get("allow_redirects", True),
-        }
-        send_kwargs.update(settings)
-
-        attempt = 0
-
-        while True:
-            is_last_attempt = attempt >= 5
-            try:
-                resp = session.send(prepared_request, **send_kwargs)
-            except (requests.exceptions.ConnectionError, OSError) as e:
-                if is_last_attempt:
-                    raise e
-            else:
-                if resp.status_code not in [502, 503, 504] or is_last_attempt:
-                    resp.raise_for_status()
-                    return resp
-
-            if not is_last_attempt:
-                attempt += 1
-                delay = 0.5 * attempt
-                self._log(
-                    "Retrying HTTP request in {} seconds.".format(delay), level="debug"
-                )
-                time.sleep(delay)
-                continue
-
-        # this should never really be hit under any sane circumstance
-        raise PoetryException("Failed HTTP {} request", method.upper())
-
-    def get_credentials_for_url(
-        self, url
-    ):  # type: (str) -> Tuple[Optional[str], Optional[str]]
-        parsed_url = urlparse.urlsplit(url)
-
-        netloc = parsed_url.netloc
-
-        credentials = self._credentials.get(netloc, (None, None))
-
-        if credentials == (None, None):
-            if "@" not in netloc:
-                credentials = self._get_credentials_for_netloc_from_config(netloc)
-            else:
-                # Split from the right because that's how urllib.parse.urlsplit()
-                # behaves if more than one @ is present (which can be checked using
-                # the password attribute of urlsplit()'s return value).
-                auth, netloc = netloc.rsplit("@", 1)
-                if ":" in auth:
-                    # Split from the left because that's how urllib.parse.urlsplit()
-                    # behaves if more than one : is present (which again can be checked
-                    # using the password attribute of the return value)
-                    credentials = auth.split(":", 1)
-                else:
-                    credentials = auth, None
-
-                credentials = tuple(
-                    None if x is None else urlparse.unquote(x) for x in credentials
-                )
-
-        if credentials[0] is not None or credentials[1] is not None:
-            credentials = (credentials[0] or "", credentials[1] or "")
-
-            self._credentials[netloc] = credentials
-
-        return credentials[0], credentials[1]
-
-    def _get_credentials_for_netloc_from_config(
-        self, netloc
-    ):  # type: (str) -> Tuple[Optional[str], Optional[str]]
-        credentials = (None, None)
-
-        for repository_name in self._config.get("repositories", []):
-            repository_config = self._config.get(
-                "repositories.{}".format(repository_name)
-            )
-            if not repository_config:
-                continue
-
-            url = repository_config.get("url")
-            if not url:
-                continue
-
-            parsed_url = urlparse.urlsplit(url)
-
-            if netloc == parsed_url.netloc:
-                auth = self._password_manager.get_http_auth(repository_name)
-
-                if auth is None:
-                    continue
-
-                return auth["username"], auth["password"]
-
-        return credentials
diff --git a/conda_lock/_vendor/poetry/installation/base_installer.py b/conda_lock/_vendor/poetry/installation/base_installer.py
index 1e068d076..9cf63a19d 100644
--- a/conda_lock/_vendor/poetry/installation/base_installer.py
+++ b/conda_lock/_vendor/poetry/installation/base_installer.py
@@ -1,9 +1,18 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+
 class BaseInstaller:
-    def install(self, package):
+    def install(self, package: Package) -> None:
         raise NotImplementedError
 
-    def update(self, source, target):
+    def update(self, source: Package, target: Package) -> None:
         raise NotImplementedError
 
-    def remove(self, package):
+    def remove(self, package: Package) -> None:
         raise NotImplementedError
diff --git a/conda_lock/_vendor/poetry/installation/chef.py b/conda_lock/_vendor/poetry/installation/chef.py
index 373980ff6..2a7ef7e1e 100644
--- a/conda_lock/_vendor/poetry/installation/chef.py
+++ b/conda_lock/_vendor/poetry/installation/chef.py
@@ -1,64 +1,42 @@
+from __future__ import annotations
+
 import hashlib
 import json
 
+from pathlib import Path
 from typing import TYPE_CHECKING
 
-from conda_lock._vendor.poetry.core.packages.utils.link import Link
-from conda_lock._vendor.poetry.utils._compat import Path
-
-from .chooser import InvalidWheelName
-from .chooser import Wheel
+from conda_lock._vendor.poetry.installation.chooser import InvalidWheelName
+from conda_lock._vendor.poetry.installation.chooser import Wheel
 
 
 if TYPE_CHECKING:
-    from typing import List
-    from typing import Optional
+    from conda_lock._vendor.poetry.core.packages.utils.link import Link
 
     from conda_lock._vendor.poetry.config.config import Config
     from conda_lock._vendor.poetry.utils.env import Env
 
 
 class Chef:
-    def __init__(self, config, env):  # type: (Config, Env) -> None
-        self._config = config
+    def __init__(self, config: Config, env: Env) -> None:
         self._env = env
         self._cache_dir = (
             Path(config.get("cache-dir")).expanduser().joinpath("artifacts")
         )
 
-    def prepare(self, archive):  # type: (Path) -> Path
-        return archive
-
-    def prepare_sdist(self, archive):  # type: (Path) -> Path
-        return archive
-
-    def prepare_wheel(self, archive):  # type: (Path) -> Path
-        return archive
-
-    def should_prepare(self, archive):  # type: (Path) -> bool
-        return not self.is_wheel(archive)
-
-    def is_wheel(self, archive):  # type: (Path) -> bool
-        return archive.suffix == ".whl"
-
-    def get_cached_archive_for_link(self, link):  # type: (Link) -> Optional[Link]
-        # If the archive is already a wheel, there is no need to cache it.
-        if link.is_wheel:
-            pass
-
+    def get_cached_archive_for_link(self, link: Link) -> Path | None:
         archives = self.get_cached_archives_for_link(link)
-
         if not archives:
-            return link
+            return None
 
-        candidates = []
+        candidates: list[tuple[float | None, Path]] = []
         for archive in archives:
-            if not archive.is_wheel:
+            if archive.suffix != ".whl":
                 candidates.append((float("inf"), archive))
                 continue
 
             try:
-                wheel = Wheel(archive.filename)
+                wheel = Wheel(archive.name)
             except InvalidWheelName:
                 continue
 
@@ -70,22 +48,22 @@ def get_cached_archive_for_link(self, link):  # type: (Link) -> Optional[Link]
             )
 
         if not candidates:
-            return link
+            return None
 
         return min(candidates)[1]
 
-    def get_cached_archives_for_link(self, link):  # type: (Link) -> List[Link]
+    def get_cached_archives_for_link(self, link: Link) -> list[Path]:
         cache_dir = self.get_cache_directory_for_link(link)
 
         archive_types = ["whl", "tar.gz", "tar.bz2", "bz2", "zip"]
-        links = []
+        paths = []
         for archive_type in archive_types:
-            for archive in cache_dir.glob("*.{}".format(archive_type)):
-                links.append(Link(archive.as_uri()))
+            for archive in cache_dir.glob(f"*.{archive_type}"):
+                paths.append(Path(archive))
 
-        return links
+        return paths
 
-    def get_cache_directory_for_link(self, link):  # type: (Link) -> Path
+    def get_cache_directory_for_link(self, link: Link) -> Path:
         key_parts = {"url": link.url_without_fragment}
 
         if link.hash_name is not None and link.hash is not None:
diff --git a/conda_lock/_vendor/poetry/installation/chooser.py b/conda_lock/_vendor/poetry/installation/chooser.py
index f205d7382..72776cdd0 100644
--- a/conda_lock/_vendor/poetry/installation/chooser.py
+++ b/conda_lock/_vendor/poetry/installation/chooser.py
@@ -1,26 +1,39 @@
+from __future__ import annotations
+
+import logging
 import re
 
-from typing import List
-from typing import Tuple
+from typing import TYPE_CHECKING
+from typing import Any
 
 from packaging.tags import Tag
 
-from conda_lock._vendor.poetry.core.packages.package import Package
-from conda_lock._vendor.poetry.core.packages.utils.link import Link
-from conda_lock._vendor.poetry.repositories.pool import Pool
-from conda_lock._vendor.poetry.utils.env import Env
+from conda_lock._vendor.poetry.config.config import Config
+from conda_lock._vendor.poetry.config.config import PackageFilterPolicy
 from conda_lock._vendor.poetry.utils.patterns import wheel_file_re
 
 
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.constraints.version import Version
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from conda_lock._vendor.poetry.core.packages.utils.link import Link
+
+    from conda_lock._vendor.poetry.repositories.repository_pool import RepositoryPool
+    from conda_lock._vendor.poetry.utils.env import Env
+
+
+logger = logging.getLogger(__name__)
+
+
 class InvalidWheelName(Exception):
     pass
 
 
-class Wheel(object):
-    def __init__(self, filename):  # type: (str) -> None
+class Wheel:
+    def __init__(self, filename: str) -> None:
         wheel_info = wheel_file_re.match(filename)
         if not wheel_info:
-            raise InvalidWheelName("{} is not a valid wheel filename.".format(filename))
+            raise InvalidWheelName(f"{filename} is not a valid wheel filename.")
 
         self.filename = filename
         self.name = wheel_info.group("name").replace("_", "-")
@@ -34,12 +47,12 @@ def __init__(self, filename):  # type: (str) -> None
             Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
         }
 
-    def get_minimum_supported_index(self, tags):
+    def get_minimum_supported_index(self, tags: list[Tag]) -> int | None:
         indexes = [tags.index(t) for t in self.tags if t in tags]
 
         return min(indexes) if indexes else None
 
-    def is_supported_by_environment(self, env):
+    def is_supported_by_environment(self, env: Env) -> bool:
         return bool(set(env.supported_tags).intersection(self.tags))
 
 
@@ -48,49 +61,63 @@ class Chooser:
     A Chooser chooses an appropriate release archive for packages.
     """
 
-    def __init__(self, pool, env):  # type: (Pool, Env) -> None
+    def __init__(
+        self, pool: RepositoryPool, env: Env, config: Config | None = None
+    ) -> None:
         self._pool = pool
         self._env = env
+        self._config = config or Config.create()
+        self._no_binary_policy: PackageFilterPolicy = PackageFilterPolicy(
+            self._config.get("installer.no-binary", [])
+        )
 
-    def choose_for(self, package):  # type: (Package) -> Link
+    def choose_for(self, package: Package) -> Link:
         """
         Return the url of the selected archive for a given package.
         """
         links = []
         for link in self._get_links(package):
-            if link.is_wheel and not Wheel(link.filename).is_supported_by_environment(
-                self._env
-            ):
-                continue
+            if link.is_wheel:
+                if not self._no_binary_policy.allows(package.name):
+                    logger.debug(
+                        "Skipping wheel for %s as requested in no binary policy for"
+                        " package (%s)",
+                        link.filename,
+                        package.name,
+                    )
+                    continue
+
+                if not Wheel(link.filename).is_supported_by_environment(self._env):
+                    logger.debug(
+                        "Skipping wheel %s as this is not supported by the current"
+                        " environment",
+                        link.filename,
+                    )
+                    continue
 
             if link.ext in {".egg", ".exe", ".msi", ".rpm", ".srpm"}:
+                logger.debug("Skipping unsupported distribution %s", link.filename)
                 continue
 
             links.append(link)
 
         if not links:
-            raise RuntimeError(
-                "Unable to find installation candidates for {}".format(package)
-            )
+            raise RuntimeError(f"Unable to find installation candidates for {package}")
 
         # Get the best link
         chosen = max(links, key=lambda link: self._sort_key(package, link))
-        if not chosen:
-            raise RuntimeError(
-                "Unable to find installation candidates for {}".format(package)
-            )
 
         return chosen
 
-    def _get_links(self, package):  # type: (Package) -> List[Link]
-        if not package.source_type:
-            if not self._pool.has_repository("pypi"):
-                repository = self._pool.repositories[0]
-            else:
-                repository = self._pool.repository("pypi")
-        else:
+    def _get_links(self, package: Package) -> list[Link]:
+        if package.source_type:
+            assert package.source_reference is not None
             repository = self._pool.repository(package.source_reference)
 
+        elif not self._pool.has_repository("pypi"):
+            repository = self._pool.repositories[0]
+        else:
+            repository = self._pool.repository("pypi")
         links = repository.find_links_for_package(package)
 
         hashes = [f["hash"] for f in package.files]
@@ -103,22 +130,29 @@ def _get_links(self, package):  # type: (Package) -> List[Link]
                 selected_links.append(link)
                 continue
 
+            assert link.hash_name is not None
             h = link.hash_name + ":" + link.hash
             if h not in hashes:
+                logger.debug(
+                    "Skipping %s as %s checksum does not match expected value",
+                    link.filename,
+                    link.hash_name,
+                )
                 continue
 
             selected_links.append(link)
 
         if links and not selected_links:
             raise RuntimeError(
-                "Retrieved digest for link {}({}) not in poetry.lock metadata {}".format(
-                    link.filename, h, hashes
-                )
+                f"Retrieved digest for link {link.filename}({h}) not in poetry.lock"
+                f" metadata {hashes}"
             )
 
         return selected_links
 
-    def _sort_key(self, package, link):  # type: (Package, Link) -> Tuple
+    def _sort_key(
+        self, package: Package, link: Link
+    ) -> tuple[int, int, int, Version, tuple[Any, ...], int]:
         """
         Function to pass as the `key` argument to a call to sorted() to sort
         InstallationCandidates by preference.
@@ -142,30 +176,31 @@ def _sort_key(self, package, link):  # type: (Package, Link) -> Tuple
               comparison operators, but then different sdist links
               with the same version, would have to be considered equal
         """
-        support_num = len(self._env.supported_tags)
-        build_tag = ()
+        build_tag: tuple[Any, ...] = ()
         binary_preference = 0
         if link.is_wheel:
             wheel = Wheel(link.filename)
             if not wheel.is_supported_by_environment(self._env):
                 raise RuntimeError(
-                    "{} is not a supported wheel for this platform. It "
-                    "can't be sorted.".format(wheel.filename)
+                    f"{wheel.filename} is not a supported wheel for this platform. It "
+                    "can't be sorted."
                 )
 
             # TODO: Binary preference
-            pri = -(wheel.get_minimum_supported_index(self._env.supported_tags))
+            pri = -(wheel.get_minimum_supported_index(self._env.supported_tags) or 0)
             if wheel.build_tag is not None:
                 match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
+                if not match:
+                    raise ValueError(f"Unable to parse build tag: {wheel.build_tag}")
                 build_tag_groups = match.groups()
                 build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
         else:  # sdist
+            support_num = len(self._env.supported_tags)
             pri = -support_num
 
         has_allowed_hash = int(self._is_link_hash_allowed_for_package(link, package))
 
-        # TODO: Proper yank value
-        yank_value = 0
+        yank_value = int(not link.yanked)
 
         return (
             has_allowed_hash,
@@ -176,12 +211,11 @@ def _sort_key(self, package, link):  # type: (Package, Link) -> Tuple
             pri,
         )
 
-    def _is_link_hash_allowed_for_package(
-        self, link, package
-    ):  # type: (Link, Package) -> bool
+    def _is_link_hash_allowed_for_package(self, link: Link, package: Package) -> bool:
         if not link.hash:
             return True
 
+        assert link.hash_name is not None
         h = link.hash_name + ":" + link.hash
 
         return h in {f["hash"] for f in package.files}
diff --git a/conda_lock/_vendor/poetry/installation/executor.py b/conda_lock/_vendor/poetry/installation/executor.py
index 2f99f0f8f..1b46222f9 100644
--- a/conda_lock/_vendor/poetry/installation/executor.py
+++ b/conda_lock/_vendor/poetry/installation/executor.py
@@ -1,103 +1,140 @@
-# -*- coding: utf-8 -*-
-from __future__ import division
+from __future__ import annotations
 
+import contextlib
+import csv
 import itertools
+import json
 import os
 import threading
 
 from concurrent.futures import ThreadPoolExecutor
 from concurrent.futures import wait
+from pathlib import Path
 from subprocess import CalledProcessError
+from typing import TYPE_CHECKING
+from typing import Any
 
-from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency
+from conda_lock._vendor.cleo.io.null_io import NullIO
 from conda_lock._vendor.poetry.core.packages.utils.link import Link
-from conda_lock._vendor.poetry.core.packages.utils.utils import url_to_path
 from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML
-from conda_lock._vendor.poetry.io.null_io import NullIO
-from conda_lock._vendor.poetry.utils._compat import PY2
-from conda_lock._vendor.poetry.utils._compat import WINDOWS
-from conda_lock._vendor.poetry.utils._compat import OrderedDict
-from conda_lock._vendor.poetry.utils._compat import Path
-from conda_lock._vendor.poetry.utils._compat import cpu_count
+
+from conda_lock._vendor.poetry.installation.chef import Chef
+from conda_lock._vendor.poetry.installation.chooser import Chooser
+from conda_lock._vendor.poetry.installation.operations import Install
+from conda_lock._vendor.poetry.installation.operations import Uninstall
+from conda_lock._vendor.poetry.installation.operations import Update
 from conda_lock._vendor.poetry.utils._compat import decode
+from conda_lock._vendor.poetry.utils.authenticator import Authenticator
 from conda_lock._vendor.poetry.utils.env import EnvCommandError
-from conda_lock._vendor.poetry.utils.helpers import safe_rmtree
-
-from .authenticator import Authenticator
-from .chef import Chef
-from .chooser import Chooser
-from .operations.install import Install
-from .operations.operation import Operation
-from .operations.uninstall import Uninstall
-from .operations.update import Update
-
-
-class Executor(object):
-    def __init__(self, env, pool, config, io, parallel=None):
+from conda_lock._vendor.poetry.utils.helpers import atomic_open
+from conda_lock._vendor.poetry.utils.helpers import get_file_hash
+from conda_lock._vendor.poetry.utils.helpers import pluralize
+from conda_lock._vendor.poetry.utils.helpers import remove_directory
+from conda_lock._vendor.poetry.utils.pip import pip_install
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.cleo.io.io import IO
+    from conda_lock._vendor.cleo.io.outputs.section_output import SectionOutput
+    from conda_lock._vendor.poetry.core.masonry.builders.builder import Builder
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.config.config import Config
+    from conda_lock._vendor.poetry.installation.operations.operation import Operation
+    from conda_lock._vendor.poetry.repositories import RepositoryPool
+    from conda_lock._vendor.poetry.utils.env import Env
+
+
+class Executor:
+    def __init__(
+        self,
+        env: Env,
+        pool: RepositoryPool,
+        config: Config,
+        io: IO,
+        parallel: bool | None = None,
+        disable_cache: bool = False,
+    ) -> None:
         self._env = env
         self._io = io
         self._dry_run = False
         self._enabled = True
         self._verbose = False
-        self._authenticator = Authenticator(config, self._io)
-        self._chef = Chef(config, self._env)
-        self._chooser = Chooser(pool, self._env)
 
         if parallel is None:
             parallel = config.get("installer.parallel", True)
 
-        if parallel and not (PY2 and WINDOWS):
-            # This should be directly handled by ThreadPoolExecutor
-            # however, on some systems the number of CPUs cannot be determined
-            # (it raises a NotImplementedError), so, in this case, we assume
-            # that the system only has one CPU.
-            try:
-                self._max_workers = cpu_count() + 4
-            except NotImplementedError:
-                self._max_workers = 5
+        if parallel:
+            self._max_workers = self._get_max_workers(
+                desired_max_workers=config.get("installer.max-workers")
+            )
         else:
             self._max_workers = 1
 
+        self._authenticator = Authenticator(
+            config, self._io, disable_cache=disable_cache, pool_size=self._max_workers
+        )
+        self._chef = Chef(config, self._env)
+        self._chooser = Chooser(pool, self._env, config)
+
         self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
         self._total_operations = 0
         self._executed_operations = 0
         self._executed = {"install": 0, "update": 0, "uninstall": 0}
         self._skipped = {"install": 0, "update": 0, "uninstall": 0}
-        self._sections = OrderedDict()
+        self._sections: dict[int, SectionOutput] = {}
+        self._yanked_warnings: list[str] = []
         self._lock = threading.Lock()
         self._shutdown = False
+        self._hashes: dict[str, str] = {}
 
     @property
-    def installations_count(self):  # type: () -> int
+    def installations_count(self) -> int:
         return self._executed["install"]
 
     @property
-    def updates_count(self):  # type: () -> int
+    def updates_count(self) -> int:
         return self._executed["update"]
 
     @property
-    def removals_count(self):  # type: () -> int
+    def removals_count(self) -> int:
         return self._executed["uninstall"]
 
-    def supports_fancy_output(self):  # type: () -> bool
-        return self._io.supports_ansi() and not self._dry_run
+    def supports_fancy_output(self) -> bool:
+        return self._io.output.is_decorated() and not self._dry_run
 
-    def disable(self):
+    def disable(self) -> Executor:
         self._enabled = False
 
         return self
 
-    def dry_run(self, dry_run=True):
+    def dry_run(self, dry_run: bool = True) -> Executor:
         self._dry_run = dry_run
 
         return self
 
-    def verbose(self, verbose=True):
+    def verbose(self, verbose: bool = True) -> Executor:
         self._verbose = verbose
 
         return self
 
-    def execute(self, operations):  # type: (Operation) -> int
+    def pip_install(
+        self, req: Path, upgrade: bool = False, editable: bool = False
+    ) -> int:
+        try:
+            pip_install(req, self._env, upgrade=upgrade, editable=editable)
+        except EnvCommandError as e:
+            output = decode(e.e.output)
+            if (
+                "KeyboardInterrupt" in output
+                or "ERROR: Operation cancelled by user" in output
+            ):
+                return -2
+            raise
+
+        return 0
+
+    def execute(self, operations: list[Operation]) -> int:
         self._total_operations = len(operations)
         for job_type in self._executed:
             self._executed[job_type] = 0
@@ -106,9 +143,18 @@ def execute(self, operations):  # type: (Operation) -> int
         if operations and (self._enabled or self._dry_run):
             self._display_summary(operations)
 
+        self._sections = {}
+        self._yanked_warnings = []
+
+        # pip has to be installed first without parallelism if we install via pip
+        for i, op in enumerate(operations):
+            if op.package.name == "pip":
+                wait([self._executor.submit(self._execute_operation, op)])
+                del operations[i]
+                break
+
         # We group operations by priority
         groups = itertools.groupby(operations, key=lambda o: -o.priority)
-        self._sections = OrderedDict()
         for _, group in groups:
             tasks = []
             serial_operations = []
@@ -116,7 +162,7 @@ def execute(self, operations):  # type: (Operation) -> int
                 if self._shutdown:
                     break
 
-                # Some operations are unsafe, we mus execute them serially in a group
+                # Some operations are unsafe, we must execute them serially in a group
                 # https://github.com/python-poetry/poetry/issues/3086
                 # https://github.com/python-poetry/poetry/issues/2658
                 #
@@ -148,9 +194,27 @@ def execute(self, operations):  # type: (Operation) -> int
 
                 break
 
+        for warning in self._yanked_warnings:
+            self._io.write_error_line(f"Warning: {warning}")
+
         return 1 if self._shutdown else 0
 
-    def _write(self, operation, line):
+    @staticmethod
+    def _get_max_workers(desired_max_workers: int | None = None) -> int:
+        # This should be directly handled by ThreadPoolExecutor
+        # however, on some systems the number of CPUs cannot be determined
+        # (it raises a NotImplementedError), so, in this case, we assume
+        # that the system only has one CPU.
+        try:
+            default_max_workers = (os.cpu_count() or 1) + 4
+        except NotImplementedError:
+            default_max_workers = 5
+
+        if desired_max_workers is None:
+            return default_max_workers
+        return min(default_max_workers, desired_max_workers)
+
+    def _write(self, operation: Operation, line: str) -> None:
         if not self.supports_fancy_output() or not self._should_write_operation(
             operation
         ):
@@ -165,38 +229,34 @@ def _write(self, operation, line):
 
         with self._lock:
             section = self._sections[id(operation)]
-            section.output.clear()
+            section.clear()
             section.write(line)
 
-    def _execute_operation(self, operation):
+    def _execute_operation(self, operation: Operation) -> None:
         try:
+            op_message = self.get_operation_message(operation)
             if self.supports_fancy_output():
-                if id(operation) not in self._sections:
-                    if self._should_write_operation(operation):
-                        with self._lock:
-                            self._sections[id(operation)] = self._io.section()
-                            self._sections[id(operation)].write_line(
-                                "  • {message}: Pending...".format(
-                                    message=self.get_operation_message(operation),
-                                ),
-                            )
+                if id(operation) not in self._sections and self._should_write_operation(
+                    operation
+                ):
+                    with self._lock:
+                        self._sections[id(operation)] = self._io.section()
+                        self._sections[id(operation)].write_line(
+                            f"  • {op_message}:"
+                            " Pending..."
+                        )
             else:
                 if self._should_write_operation(operation):
                     if not operation.skipped:
                         self._io.write_line(
-                            "  • {message}".format(
-                                message=self.get_operation_message(operation),
-                            ),
+                            f"  • {op_message}"
                         )
                     else:
                         self._io.write_line(
-                            "  • {message}: "
+                            f"  • {op_message}: "
                             "Skipped "
                             "for the following reason: "
-                            "{reason}".format(
-                                message=self.get_operation_message(operation),
-                                reason=operation.skip_reason,
-                            )
+                            f"{operation.skip_reason}"
                         )
 
             try:
@@ -212,15 +272,18 @@ def _execute_operation(self, operation):
             # error to be picked up by the error handler.
             if result == -2:
                 raise KeyboardInterrupt
-        except Exception as e:
+        except Exception as e:  # noqa: PIE786
             try:
-                from clikit.ui.components.exception_trace import ExceptionTrace
+                from conda_lock._vendor.cleo.ui.exception_trace import ExceptionTrace
 
+                io: IO | SectionOutput
                 if not self.supports_fancy_output():
                     io = self._io
                 else:
-                    message = "   {message}: Failed".format(
-                        message=self.get_operation_message(operation, error=True),
+                    message = (
+                        "  "
+                        f" {self.get_operation_message(operation, error=True)}:"
+                        " Failed"
                     )
                     self._write(operation, message)
                     io = self._sections.get(id(operation), self._io)
@@ -234,8 +297,10 @@ def _execute_operation(self, operation):
                     self._shutdown = True
         except KeyboardInterrupt:
             try:
-                message = "   {message}: Cancelled".format(
-                    message=self.get_operation_message(operation, warning=True),
+                message = (
+                    "  "
+                    f" {self.get_operation_message(operation, warning=True)}:"
+                    " Cancelled"
                 )
                 if not self.supports_fancy_output():
                     self._io.write_line(message)
@@ -245,7 +310,7 @@ def _execute_operation(self, operation):
                 with self._lock:
                     self._shutdown = True
 
-    def _do_execute_operation(self, operation):
+    def _do_execute_operation(self, operation: Operation) -> int:
         method = operation.job_type
 
         operation_message = self.get_operation_message(operation)
@@ -253,12 +318,10 @@ def _do_execute_operation(self, operation):
             if self.supports_fancy_output():
                 self._write(
                     operation,
-                    "  • {message}: "
+                    f"  • {operation_message}: "
                     "Skipped "
                     "for the following reason: "
-                    "{reason}".format(
-                        message=operation_message, reason=operation.skip_reason,
-                    ),
+                    f"{operation.skip_reason}",
                 )
 
             self._skipped[operation.job_type] += 1
@@ -266,29 +329,22 @@ def _do_execute_operation(self, operation):
             return 0
 
         if not self._enabled or self._dry_run:
-            self._io.write_line(
-                "  • {message}".format(
-                    message=operation_message,
-                )
-            )
-
             return 0
 
-        result = getattr(self, "_execute_{}".format(method))(operation)
+        result: int = getattr(self, f"_execute_{method}")(operation)
 
         if result != 0:
             return result
 
-        message = "  • {message}".format(
-            message=self.get_operation_message(operation, done=True),
-        )
+        operation_message = self.get_operation_message(operation, done=True)
+        message = f"  • {operation_message}"
         self._write(operation, message)
 
         self._increment_operations_count(operation, True)
 
         return result
 
-    def _increment_operations_count(self, operation, executed):
+    def _increment_operations_count(self, operation: Operation, executed: bool) -> None:
         with self._lock:
             if executed:
                 self._executed_operations += 1
@@ -296,7 +352,7 @@ def _increment_operations_count(self, operation, executed):
             else:
                 self._skipped[operation.job_type] += 1
 
-    def run_pip(self, *args, **kwargs):  # type: (...) -> int
+    def run_pip(self, *args: Any, **kwargs: Any) -> int:
         try:
             self._env.run_pip(*args, **kwargs)
         except EnvCommandError as e:
@@ -311,7 +367,13 @@ def run_pip(self, *args, **kwargs):  # type: (...) -> int
 
         return 0
 
-    def get_operation_message(self, operation, done=False, error=False, warning=False):
+    def get_operation_message(
+        self,
+        operation: Operation,
+        done: bool = False,
+        error: bool = False,
+        warning: bool = False,
+    ) -> str:
         base_tag = "fg=default"
         operation_color = "c2"
         source_operation_color = "c2"
@@ -330,42 +392,32 @@ def get_operation_message(self, operation, done=False, error=False, warning=Fals
             source_operation_color += "_dark"
             package_color += "_dark"
 
-        if operation.job_type == "install":
-            return "<{}>Installing <{}>{} (<{}>{})".format(
-                base_tag,
-                package_color,
-                operation.package.name,
-                package_color,
-                operation_color,
-                operation.package.full_pretty_version,
+        if isinstance(operation, Install):
+            return (
+                f"<{base_tag}>Installing"
+                f" <{package_color}>{operation.package.name}"
+                f" (<{operation_color}>{operation.package.full_pretty_version})"
             )
 
-        if operation.job_type == "uninstall":
-            return "<{}>Removing <{}>{} (<{}>{})".format(
-                base_tag,
-                package_color,
-                operation.package.name,
-                package_color,
-                operation_color,
-                operation.package.full_pretty_version,
+        if isinstance(operation, Uninstall):
+            return (
+                f"<{base_tag}>Removing"
+                f" <{package_color}>{operation.package.name}"
+                f" (<{operation_color}>{operation.package.full_pretty_version})"
             )
 
-        if operation.job_type == "update":
-            return "<{}>Updating <{}>{} (<{}>{} -> <{}>{})".format(
-                base_tag,
-                package_color,
-                operation.initial_package.name,
-                package_color,
-                source_operation_color,
-                operation.initial_package.full_pretty_version,
-                source_operation_color,
-                operation_color,
-                operation.target_package.full_pretty_version,
+        if isinstance(operation, Update):
+            return (
+                f"<{base_tag}>Updating"
+                f" <{package_color}>{operation.initial_package.name} "
+                f"(<{source_operation_color}>"
+                f"{operation.initial_package.full_pretty_version}"
+                f" -> <{operation_color}>"
+                f"{operation.target_package.full_pretty_version})"
             )
-
         return ""
 
-    def _display_summary(self, operations):
+    def _display_summary(self, operations: list[Operation]) -> None:
         installs = 0
         updates = 0
         uninstalls = 0
@@ -389,40 +441,37 @@ def _display_summary(self, operations):
             return
 
         self._io.write_line("")
-        self._io.write_line(
-            "Package operations: "
-            "{} install{}, "
-            "{} update{}, "
-            "{} removal{}"
-            "{}".format(
-                installs,
-                "" if installs == 1 else "s",
-                updates,
-                "" if updates == 1 else "s",
-                uninstalls,
-                "" if uninstalls == 1 else "s",
-                ", {} skipped".format(skipped)
-                if skipped and self._verbose
-                else "",
-            )
-        )
+        self._io.write("Package operations: ")
+        self._io.write(f"{installs} install{pluralize(installs)}, ")
+        self._io.write(f"{updates} update{pluralize(updates)}, ")
+        self._io.write(f"{uninstalls} removal{pluralize(uninstalls)}")
+        if skipped and self._verbose:
+            self._io.write(f", {skipped} skipped")
+        self._io.write_line("")
         self._io.write_line("")
 
-    def _execute_install(self, operation):  # type: (Install) -> None
-        return self._install(operation)
+    def _execute_install(self, operation: Install | Update) -> int:
+        status_code = self._install(operation)
 
-    def _execute_update(self, operation):  # type: (Update) -> None
-        return self._update(operation)
+        self._save_url_reference(operation)
 
-    def _execute_uninstall(self, operation):  # type: (Uninstall) -> None
-        message = "  • {message}: Removing...".format(
-            message=self.get_operation_message(operation),
-        )
+        return status_code
+
+    def _execute_update(self, operation: Install | Update) -> int:
+        status_code = self._update(operation)
+
+        self._save_url_reference(operation)
+
+        return status_code
+
+    def _execute_uninstall(self, operation: Uninstall) -> int:
+        op_msg = self.get_operation_message(operation)
+        message = f"  • {op_msg}: Removing..."
         self._write(operation, message)
 
         return self._remove(operation)
 
-    def _install(self, operation):
+    def _install(self, operation: Install | Update) -> int:
         package = operation.package
         if package.source_type == "directory":
             return self._install_directory(operation)
@@ -433,33 +482,30 @@ def _install(self, operation):
         if package.source_type == "file":
             archive = self._prepare_file(operation)
         elif package.source_type == "url":
+            assert package.source_url is not None
             archive = self._download_link(operation, Link(package.source_url))
         else:
             archive = self._download(operation)
 
         operation_message = self.get_operation_message(operation)
-        message = "  • {message}: Installing...".format(
-            message=operation_message,
+        message = (
+            f"  • {operation_message}:"
+            " Installing..."
         )
         self._write(operation, message)
+        return self.pip_install(archive, upgrade=operation.job_type == "update")
 
-        args = ["install", "--no-deps", str(archive)]
-        if operation.job_type == "update":
-            args.insert(2, "-U")
-
-        return self.run_pip(*args)
-
-    def _update(self, operation):
+    def _update(self, operation: Install | Update) -> int:
         return self._install(operation)
 
-    def _remove(self, operation):
+    def _remove(self, operation: Uninstall) -> int:
         package = operation.package
 
         # If we have a VCS package, remove its source directory
         if package.source_type == "git":
             src_dir = self._env.path / "src" / package.name
             if src_dir.exists():
-                safe_rmtree(str(src_dir))
+                remove_directory(src_dir, force=True)
 
         try:
             return self.run_pip("uninstall", package.name, "-y")
@@ -469,132 +515,135 @@ def _remove(self, operation):
 
             raise
 
-    def _prepare_file(self, operation):
+    def _prepare_file(self, operation: Install | Update) -> Path:
         package = operation.package
+        operation_message = self.get_operation_message(operation)
 
-        message = "  • {message}: Preparing...".format(
-            message=self.get_operation_message(operation),
+        message = (
+            f"  • {operation_message}:"
+            " Preparing..."
         )
         self._write(operation, message)
 
+        assert package.source_url is not None
         archive = Path(package.source_url)
         if not Path(package.source_url).is_absolute() and package.root_dir:
             archive = package.root_dir / archive
 
-        archive = self._chef.prepare(archive)
-
         return archive
 
-    def _install_directory(self, operation):
+    def _install_directory(self, operation: Install | Update) -> int:
         from conda_lock._vendor.poetry.factory import Factory
 
         package = operation.package
         operation_message = self.get_operation_message(operation)
 
-        message = "  • {message}: Building...".format(
-            message=operation_message,
+        message = (
+            f"  • {operation_message}:"
+            " Building..."
         )
         self._write(operation, message)
 
+        assert package.source_url is not None
         if package.root_dir:
-            req = os.path.join(str(package.root_dir), package.source_url)
+            req = package.root_dir / package.source_url
         else:
-            req = os.path.realpath(package.source_url)
+            req = Path(package.source_url).resolve(strict=False)
 
-        args = ["install", "--no-deps", "-U"]
+        if package.source_subdirectory:
+            req /= package.source_subdirectory
 
         pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))
 
+        package_poetry = None
         if pyproject.is_poetry_project():
+            with contextlib.suppress(RuntimeError):
+                package_poetry = Factory().create_poetry(pyproject.file.path.parent)
+
+        if package_poetry is not None:
             # Even if there is a build system specified
             # some versions of pip (< 19.0.0) don't understand it
             # so we need to check the version of pip to know
             # if we can rely on the build system
-            legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
-                19, 0, 0
+            legacy_pip = (
+                self._env.pip_version
+                < self._env.pip_version.__class__.from_parts(19, 0, 0)
             )
 
-            try:
-                package_poetry = Factory().create_poetry(pyproject.file.path.parent)
-            except RuntimeError:
-                package_poetry = None
+            builder: Builder
+            if package.develop and not package_poetry.package.build_script:
+                from conda_lock._vendor.poetry.masonry.builders.editable import EditableBuilder
 
-            if package_poetry is not None:
-                if package.develop and not package_poetry.package.build_script:
-                    from conda_lock._vendor.poetry.masonry.builders.editable import EditableBuilder
+                # This is a Poetry package in editable mode
+                # we can use the EditableBuilder without going through pip
+                # to install it, unless it has a build script.
+                builder = EditableBuilder(package_poetry, self._env, NullIO())
+                builder.build()
 
-                    # This is a Poetry package in editable mode
-                    # we can use the EditableBuilder without going through pip
-                    # to install it, unless it has a build script.
-                    builder = EditableBuilder(package_poetry, self._env, NullIO())
-                    builder.build()
-
-                    return 0
-                elif legacy_pip or package_poetry.package.build_script:
-                    from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder
-
-                    # We need to rely on creating a temporary setup.py
-                    # file since the version of pip does not support
-                    # build-systems
-                    # We also need it for non-PEP-517 packages
-                    builder = SdistBuilder(package_poetry)
-
-                    with builder.setup_py():
-                        if package.develop:
-                            args.append("-e")
-
-                        args.append(req)
-
-                        return self.run_pip(*args)
-
-        if package.develop:
-            args.append("-e")
+                return 0
+            elif legacy_pip or package_poetry.package.build_script:
+                from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder
 
-        args.append(req)
+                # We need to rely on creating a temporary setup.py
+                # file since the version of pip does not support
+                # build-systems
+                # We also need it for non-PEP-517 packages
+                builder = SdistBuilder(package_poetry)
+                with builder.setup_py():
+                    return self.pip_install(req, upgrade=True, editable=package.develop)
 
-        return self.run_pip(*args)
+        return self.pip_install(req, upgrade=True, editable=package.develop)
 
-    def _install_git(self, operation):
-        from conda_lock._vendor.poetry.core.vcs import Git
+    def _install_git(self, operation: Install | Update) -> int:
+        from conda_lock._vendor.poetry.vcs.git import Git
 
         package = operation.package
         operation_message = self.get_operation_message(operation)
 
-        message = "  • {message}: Cloning...".format(
-            message=operation_message,
+        message = (
+            f"  • {operation_message}: Cloning..."
         )
         self._write(operation, message)
 
-        src_dir = self._env.path / "src" / package.name
-        if src_dir.exists():
-            safe_rmtree(str(src_dir))
-
-        src_dir.parent.mkdir(exist_ok=True)
-
-        git = Git()
-        git.clone(package.source_url, src_dir)
+        assert package.source_url is not None
+        source = Git.clone(
+            url=package.source_url,
+            source_root=self._env.path / "src",
+            revision=package.source_resolved_reference or package.source_reference,
+        )
 
-        reference = package.source_resolved_reference
-        if not reference:
-            reference = package.source_reference
+        # Now we just need to install from the source directory
+        original_url = package.source_url
+        package._source_url = str(source.path)
 
-        git.checkout(reference, src_dir)
+        status_code = self._install_directory(operation)
 
-        # Now we just need to install from the source directory
-        package._source_url = str(src_dir)
+        package._source_url = original_url
 
-        return self._install_directory(operation)
+        return status_code
 
-    def _download(self, operation):  # type: (Operation) -> Path
+    def _download(self, operation: Install | Update) -> Path:
         link = self._chooser.choose_for(operation.package)
 
+        if link.yanked:
+            # Store yanked warnings in a list and print after installing, so they can't
+            # be overlooked. Further, printing them in the concerning section would have
+            # the risk of overwriting the warning, so it is only briefly visible.
+            message = (
+                f"The file chosen for install of {operation.package.pretty_name} "
+                f"{operation.package.pretty_version} ({link.show_url}) is yanked."
+            )
+            if link.yanked_reason:
+                message += f" Reason for being yanked: {link.yanked_reason}"
+            self._yanked_warnings.append(message)
+
         return self._download_link(operation, link)
 
-    def _download_link(self, operation, link):
+    def _download_link(self, operation: Install | Update, link: Link) -> Path:
         package = operation.package
 
         archive = self._chef.get_cached_archive_for_link(link)
-        if archive is link:
+        if archive is None:
             # No cached distributions was found, so we download and prepare it
             try:
                 archive = self._download_archive(operation, link)
@@ -602,73 +651,62 @@ def _download_link(self, operation, link):
                 cache_directory = self._chef.get_cache_directory_for_link(link)
                 cached_file = cache_directory.joinpath(link.filename)
                 # We can't use unlink(missing_ok=True) because it's not available
-                # in pathlib2 for Python 2.7
+                # prior to Python 3.8
                 if cached_file.exists():
                     cached_file.unlink()
 
                 raise
 
-            # TODO: Check readability of the created archive
-
-            if not link.is_wheel:
-                archive = self._chef.prepare(archive)
-
         if package.files:
-            hashes = {f["hash"] for f in package.files}
-            hash_types = {h.split(":")[0] for h in hashes}
-            archive_hashes = set()
-            archive_path = (
-                url_to_path(archive.url) if isinstance(archive, Link) else archive
-            )
-            for hash_type in hash_types:
-                archive_hashes.add(
-                    "{}:{}".format(
-                        hash_type,
-                        FileDependency(package.name, archive_path).hash(hash_type),
-                    )
-                )
+            archive_hash = self._validate_archive_hash(archive, package)
 
-            if archive_hashes.isdisjoint(hashes):
-                raise RuntimeError(
-                    "Invalid hashes ({}) for {} using archive {}. Expected one of {}.".format(
-                        ", ".join(sorted(archive_hashes)),
-                        package,
-                        archive_path.name,
-                        ", ".join(sorted(hashes)),
-                    )
-                )
+            self._hashes[package.name] = archive_hash
 
         return archive
 
-    def _download_archive(self, operation, link):  # type: (Operation, Link) -> Path
+    @staticmethod
+    def _validate_archive_hash(archive: Path, package: Package) -> str:
+        archive_hash: str = "sha256:" + get_file_hash(archive)
+        known_hashes = {f["hash"] for f in package.files}
+
+        if archive_hash not in known_hashes:
+            raise RuntimeError(
+                f"Hash for {package} from archive {archive.name} not found in"
+                f" known hashes (was: {archive_hash})"
+            )
+
+        return archive_hash
+
+    def _download_archive(self, operation: Install | Update, link: Link) -> Path:
         response = self._authenticator.request(
             "get", link.url, stream=True, io=self._sections.get(id(operation), self._io)
         )
         wheel_size = response.headers.get("content-length")
         operation_message = self.get_operation_message(operation)
-        message = "  • {message}: Downloading...".format(
-            message=operation_message,
+        message = (
+            f"  • {operation_message}: Downloading..."
         )
         progress = None
         if self.supports_fancy_output():
             if wheel_size is None:
                 self._write(operation, message)
             else:
-                from clikit.ui.components.progress_bar import ProgressBar
+                from conda_lock._vendor.cleo.ui.progress_bar import ProgressBar
 
                 progress = ProgressBar(
-                    self._sections[id(operation)].output, max=int(wheel_size)
+                    self._sections[id(operation)], max=int(wheel_size)
                 )
                 progress.set_format(message + " %percent%%")
 
         if progress:
             with self._lock:
+                self._sections[id(operation)].clear()
                 progress.start()
 
         done = 0
         archive = self._chef.get_cache_directory_for_link(link) / link.filename
         archive.parent.mkdir(parents=True, exist_ok=True)
-        with archive.open("wb") as f:
+        with atomic_open(archive) as f:
             for chunk in response.iter_content(chunk_size=4096):
                 if not chunk:
                     break
@@ -687,8 +725,107 @@ def _download_archive(self, operation, link):  # type: (Operation, Link) -> Path
 
         return archive
 
-    def _should_write_operation(self, operation):  # type: (Operation) -> bool
-        if not operation.skipped:
-            return True
+    def _should_write_operation(self, operation: Operation) -> bool:
+        return (
+            not operation.skipped or self._dry_run or self._verbose or not self._enabled
+        )
+
+    def _save_url_reference(self, operation: Operation) -> None:
+        """
+        Create and store a PEP-610 `direct_url.json` file, if needed.
+        """
+        if operation.job_type not in {"install", "update"}:
+            return
+
+        package = operation.package
+
+        if not package.source_url or package.source_type == "legacy":
+            # Since we are installing from our own distribution cache
+            # pip will write a `direct_url.json` file pointing to the cache
+            # distribution.
+            # That's not what we want, so we remove the direct_url.json file,
+            # if it exists.
+            for (
+                direct_url_json
+            ) in self._env.site_packages.find_distribution_direct_url_json_files(
+                distribution_name=package.name, writable_only=True
+            ):
+                # We can't use unlink(missing_ok=True) because it's not always available
+                if direct_url_json.exists():
+                    direct_url_json.unlink()
+            return
+
+        url_reference: dict[str, Any] | None = None
+
+        if package.source_type == "git":
+            url_reference = self._create_git_url_reference(package)
+        elif package.source_type == "url":
+            url_reference = self._create_url_url_reference(package)
+        elif package.source_type == "directory":
+            url_reference = self._create_directory_url_reference(package)
+        elif package.source_type == "file":
+            url_reference = self._create_file_url_reference(package)
+
+        if url_reference:
+            for dist in self._env.site_packages.distributions(
+                name=package.name, writable_only=True
+            ):
+                dist_path = dist._path  # type: ignore[attr-defined]
+                assert isinstance(dist_path, Path)
+                url = dist_path / "direct_url.json"
+                url.write_text(json.dumps(url_reference), encoding="utf-8")
+
+                record = dist_path / "RECORD"
+                if record.exists():
+                    with record.open(mode="a", encoding="utf-8", newline="") as f:
+                        writer = csv.writer(f)
+                        path = url.relative_to(record.parent.parent)
+                        writer.writerow([str(path), "", ""])
+
+    def _create_git_url_reference(self, package: Package) -> dict[str, Any]:
+        reference = {
+            "url": package.source_url,
+            "vcs_info": {
+                "vcs": "git",
+                "requested_revision": package.source_reference,
+                "commit_id": package.source_resolved_reference,
+            },
+        }
+        if package.source_subdirectory:
+            reference["subdirectory"] = package.source_subdirectory
+
+        return reference
+
+    def _create_url_url_reference(self, package: Package) -> dict[str, Any]:
+        archive_info = {}
+
+        if package.name in self._hashes:
+            archive_info["hash"] = self._hashes[package.name]
+
+        reference = {"url": package.source_url, "archive_info": archive_info}
+
+        return reference
+
+    def _create_file_url_reference(self, package: Package) -> dict[str, Any]:
+        archive_info = {}
+
+        if package.name in self._hashes:
+            archive_info["hash"] = self._hashes[package.name]
+
+        assert package.source_url is not None
+        return {
+            "url": Path(package.source_url).as_uri(),
+            "archive_info": archive_info,
+        }
+
+    def _create_directory_url_reference(self, package: Package) -> dict[str, Any]:
+        dir_info = {}
+
+        if package.develop:
+            dir_info["editable"] = True
 
-        return self._dry_run or self._verbose
+        assert package.source_url is not None
+        return {
+            "url": Path(package.source_url).as_uri(),
+            "dir_info": dir_info,
+        }
diff --git a/conda_lock/_vendor/poetry/installation/installer.py b/conda_lock/_vendor/poetry/installation/installer.py
index c09ad9732..297876e5e 100644
--- a/conda_lock/_vendor/poetry/installation/installer.py
+++ b/conda_lock/_vendor/poetry/installation/installer.py
@@ -1,40 +1,50 @@
-from typing import List
-from typing import Optional
-from typing import Union
+from __future__ import annotations
 
-from clikit.api.io import IO
+from typing import TYPE_CHECKING
 
-from conda_lock._vendor.poetry.config.config import Config
-from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
-from conda_lock._vendor.poetry.io.null_io import NullIO
-from conda_lock._vendor.poetry.packages import Locker
-from conda_lock._vendor.poetry.repositories import Pool
+from conda_lock._vendor.cleo.io.null_io import NullIO
+from packaging.utils import canonicalize_name
+
+from conda_lock._vendor.poetry.installation.executor import Executor
+from conda_lock._vendor.poetry.installation.operations import Install
+from conda_lock._vendor.poetry.installation.operations import Uninstall
+from conda_lock._vendor.poetry.installation.operations import Update
+from conda_lock._vendor.poetry.installation.pip_installer import PipInstaller
 from conda_lock._vendor.poetry.repositories import Repository
+from conda_lock._vendor.poetry.repositories import RepositoryPool
 from conda_lock._vendor.poetry.repositories.installed_repository import InstalledRepository
+from conda_lock._vendor.poetry.repositories.lockfile_repository import LockfileRepository
 from conda_lock._vendor.poetry.utils.extras import get_extra_package_names
-from conda_lock._vendor.poetry.utils.helpers import canonicalize_name
+from conda_lock._vendor.poetry.utils.helpers import pluralize
+
+
+if TYPE_CHECKING:
+    from collections.abc import Iterable
+
+    from conda_lock._vendor.cleo.io.io import IO
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
 
-from .base_installer import BaseInstaller
-from .executor import Executor
-from .operations import Install
-from .operations import Uninstall
-from .operations import Update
-from .operations.operation import Operation
-from .pip_installer import PipInstaller
+    from conda_lock._vendor.poetry.config.config import Config
+    from conda_lock._vendor.poetry.installation.base_installer import BaseInstaller
+    from conda_lock._vendor.poetry.installation.operations.operation import Operation
+    from conda_lock._vendor.poetry.packages import Locker
+    from conda_lock._vendor.poetry.utils.env import Env
 
 
 class Installer:
     def __init__(
         self,
-        io,  # type: IO
-        env,
-        package,  # type: ProjectPackage
-        locker,  # type: Locker
-        pool,  # type: Pool
-        config,  # type: Config
-        installed=None,  # type: Union[InstalledRepository, None]
-        executor=None,  # type: Optional[Executor]
-    ):
+        io: IO,
+        env: Env,
+        package: ProjectPackage,
+        locker: Locker,
+        pool: RepositoryPool,
+        config: Config,
+        installed: Repository | None = None,
+        executor: Executor | None = None,
+        disable_cache: bool = False,
+    ) -> None:
         self._io = io
         self._env = env
         self._package = package
@@ -42,20 +52,23 @@ def __init__(
         self._pool = pool
 
         self._dry_run = False
-        self._remove_untracked = False
+        self._requires_synchronization = False
         self._update = False
         self._verbose = False
         self._write_lock = True
-        self._dev_mode = True
+        self._groups: Iterable[str] | None = None
+
         self._execute_operations = True
         self._lock = False
 
-        self._whitelist = []
+        self._whitelist: list[NormalizedName] = []
 
-        self._extras = []
+        self._extras: list[NormalizedName] = []
 
         if executor is None:
-            executor = Executor(self._env, self._pool, config, self._io)
+            executor = Executor(
+                self._env, self._pool, config, self._io, disable_cache=disable_cache
+            )
 
         self._executor = executor
         self._use_executor = False
@@ -67,24 +80,24 @@ def __init__(
         self._installed_repository = installed
 
     @property
-    def executor(self):
+    def executor(self) -> Executor:
         return self._executor
 
     @property
-    def installer(self):
+    def installer(self) -> BaseInstaller:
         return self._installer
 
-    def set_package(self, package):  # type: (ProjectPackage) -> Installer
+    def set_package(self, package: ProjectPackage) -> Installer:
         self._package = package
 
         return self
 
-    def set_locker(self, locker):  # type: (Locker) -> Installer
+    def set_locker(self, locker: Locker) -> Installer:
         self._locker = locker
 
         return self
 
-    def run(self):
+    def run(self) -> int:
         # Check if refresh
         if not self._update and self._lock and self._locker.is_locked():
             return self._do_refresh()
@@ -98,50 +111,44 @@ def run(self):
             self._write_lock = False
             self._execute_operations = False
 
-        local_repo = Repository()
+        return self._do_install()
 
-        return self._do_install(local_repo)
-
-    def dry_run(self, dry_run=True):  # type: (bool) -> Installer
+    def dry_run(self, dry_run: bool = True) -> Installer:
         self._dry_run = dry_run
         self._executor.dry_run(dry_run)
 
         return self
 
-    def is_dry_run(self):  # type: () -> bool
+    def is_dry_run(self) -> bool:
         return self._dry_run
 
-    def remove_untracked(self, remove_untracked=True):  # type: (bool) -> Installer
-        self._remove_untracked = remove_untracked
+    def requires_synchronization(
+        self, requires_synchronization: bool = True
+    ) -> Installer:
+        self._requires_synchronization = requires_synchronization
 
         return self
 
-    def is_remove_untracked(self):  # type: () -> bool
-        return self._remove_untracked
-
-    def verbose(self, verbose=True):  # type: (bool) -> Installer
+    def verbose(self, verbose: bool = True) -> Installer:
         self._verbose = verbose
         self._executor.verbose(verbose)
 
         return self
 
-    def is_verbose(self):  # type: () -> bool
+    def is_verbose(self) -> bool:
         return self._verbose
 
-    def dev_mode(self, dev_mode=True):  # type: (bool) -> Installer
-        self._dev_mode = dev_mode
+    def only_groups(self, groups: Iterable[str]) -> Installer:
+        self._groups = groups
 
         return self
 
-    def is_dev_mode(self):  # type: () -> bool
-        return self._dev_mode
-
-    def update(self, update=True):  # type: (bool) -> Installer
+    def update(self, update: bool = True) -> Installer:
         self._update = update
 
         return self
 
-    def lock(self, update=True):  # type: (bool) -> Installer
+    def lock(self, update: bool = True) -> Installer:
         """
         Prepare the installer for locking only.
         """
@@ -151,10 +158,10 @@ def lock(self, update=True):  # type: (bool) -> Installer
 
         return self
 
-    def is_updating(self):  # type: () -> bool
+    def is_updating(self) -> bool:
         return self._update
 
-    def execute_operations(self, execute=True):  # type: (bool) -> Installer
+    def execute_operations(self, execute: bool = True) -> Installer:
         self._execute_operations = execute
 
         if not execute:
@@ -162,54 +169,63 @@ def execute_operations(self, execute=True):  # type: (bool) -> Installer
 
         return self
 
-    def whitelist(self, packages):  # type: (dict) -> Installer
+    def whitelist(self, packages: Iterable[str]) -> Installer:
         self._whitelist = [canonicalize_name(p) for p in packages]
 
         return self
 
-    def extras(self, extras):  # type: (list) -> Installer
-        self._extras = extras
+    def extras(self, extras: list[str]) -> Installer:
+        self._extras = [canonicalize_name(extra) for extra in extras]
 
         return self
 
-    def use_executor(self, use_executor=True):  # type: (bool) -> Installer
+    def use_executor(self, use_executor: bool = True) -> Installer:
         self._use_executor = use_executor
 
         return self
 
-    def _do_refresh(self):
-        from conda_lock._vendor.poetry.puzzle import Solver
+    def _do_refresh(self) -> int:
+        from conda_lock._vendor.poetry.puzzle.solver import Solver
 
         # Checking extras
         for extra in self._extras:
             if extra not in self._package.extras:
-                raise ValueError("Extra [{}] is not specified.".format(extra))
+                raise ValueError(f"Extra [{extra}] is not specified.")
 
-        locked_repository = self._locker.locked_repository(True)
+        locked_repository = self._locker.locked_repository()
         solver = Solver(
             self._package,
             self._pool,
-            locked_repository,
-            locked_repository,
-            self._io,  # noqa
+            locked_repository.packages,
+            locked_repository.packages,
+            self._io,
         )
 
-        ops = solver.solve(use_latest=[])
+        # Always re-solve directory dependencies, otherwise we can't determine
+        # if anything has changed (and the lock file contains an invalid version).
+        use_latest = [
+            p.name for p in locked_repository.packages if p.source_type == "directory"
+        ]
+
+        with solver.provider.use_source_root(
+            source_root=self._env.path.joinpath("src")
+        ):
+            ops = solver.solve(use_latest=use_latest).calculate_operations()
 
-        local_repo = Repository()
-        self._populate_local_repo(local_repo, ops)
+        lockfile_repo = LockfileRepository()
+        self._populate_lockfile_repo(lockfile_repo, ops)
 
-        self._write_lock_file(local_repo, force=True)
+        self._write_lock_file(lockfile_repo, force=True)
 
         return 0
 
-    def _do_install(self, local_repo):
-        from conda_lock._vendor.poetry.puzzle import Solver
+    def _do_install(self) -> int:
+        from conda_lock._vendor.poetry.puzzle.solver import Solver
 
-        locked_repository = Repository()
+        locked_repository = Repository("poetry-locked")
         if self._update:
-            if self._locker.is_locked() and not self._lock:
-                locked_repository = self._locker.locked_repository(True)
+            if not self._lock and self._locker.is_locked():
+                locked_repository = self._locker.locked_repository()
 
                 # If no packages have been whitelisted (The ones we want to update),
                 # we whitelist every package in the lock file.
@@ -220,56 +236,62 @@ def _do_install(self, local_repo):
             # Checking extras
             for extra in self._extras:
                 if extra not in self._package.extras:
-                    raise ValueError("Extra [{}] is not specified.".format(extra))
+                    raise ValueError(f"Extra [{extra}] is not specified.")
 
             self._io.write_line("Updating dependencies")
             solver = Solver(
                 self._package,
                 self._pool,
-                self._installed_repository,
-                locked_repository,
+                self._installed_repository.packages,
+                locked_repository.packages,
                 self._io,
-                remove_untracked=self._remove_untracked,
             )
 
-            ops = solver.solve(use_latest=self._whitelist)
+            with solver.provider.use_source_root(
+                source_root=self._env.path.joinpath("src")
+            ):
+                ops = solver.solve(use_latest=self._whitelist).calculate_operations()
         else:
             self._io.write_line("Installing dependencies from lock file")
 
-            locked_repository = self._locker.locked_repository(True)
+            locked_repository = self._locker.locked_repository()
 
             if not self._locker.is_fresh():
-                self._io.write_line(
+                self._io.write_error_line(
                     ""
-                    "Warning: The lock file is not up to date with "
-                    "the latest changes in pyproject.toml. "
-                    "You may be getting outdated dependencies. "
-                    "Run update to update them."
+                    "Warning: poetry.lock is not consistent with pyproject.toml. "
+                    "You may be getting improper dependencies. "
+                    "Run `poetry lock [--no-update]` to fix it."
                     ""
                 )
 
+            locker_extras = {
+                canonicalize_name(extra)
+                for extra in self._locker.lock_data.get("extras", {})
+            }
             for extra in self._extras:
-                if extra not in self._locker.lock_data.get("extras", {}):
-                    raise ValueError("Extra [{}] is not specified.".format(extra))
+                if extra not in locker_extras:
+                    raise ValueError(f"Extra [{extra}] is not specified.")
 
             # If we are installing from lock
             # Filter the operations by comparing it with what is
             # currently installed
             ops = self._get_operations_from_lock(locked_repository)
 
-        self._populate_local_repo(local_repo, ops)
+        lockfile_repo = LockfileRepository()
+        self._populate_lockfile_repo(lockfile_repo, ops)
 
         if self._update:
-            self._write_lock_file(local_repo)
+            self._write_lock_file(lockfile_repo)
 
             if self._lock:
                 # If we are only in lock mode, no need to go any further
                 return 0
 
-        root = self._package
-        if not self.is_dev_mode():
-            root = root.clone()
-            del root.dev_requires[:]
+        if self._groups is not None:
+            root = self._package.with_dependency_groups(list(self._groups), only=True)
+        else:
+            root = self._package.without_optional_dependency_groups()
 
         if self._io.is_verbose():
             self._io.write_line("")
@@ -278,13 +300,13 @@ def _do_install(self, local_repo):
             )
 
         # We resolve again by only using the lock file
-        pool = Pool(ignore_repository_names=True)
+        pool = RepositoryPool(ignore_repository_names=True)
 
         # Making a new repo containing the packages
         # newly resolved and the ones from the current lock file
-        repo = Repository()
-        for package in local_repo.packages + locked_repository.packages:
-            if not repo.has_package(package):
+        repo = Repository("poetry-repo")
+        for package in lockfile_repo.packages + locked_repository.packages:
+            if not package.is_direct_origin() and not repo.has_package(package):
                 repo.add_package(package)
 
         pool.add_repository(repo)
@@ -292,35 +314,55 @@ def _do_install(self, local_repo):
         solver = Solver(
             root,
             pool,
-            self._installed_repository,
-            locked_repository,
+            self._installed_repository.packages,
+            locked_repository.packages,
             NullIO(),
-            remove_untracked=self._remove_untracked,
         )
         # Everything is resolved at this point, so we no longer need
         # to load deferred dependencies (i.e. VCS, URL and path dependencies)
         solver.provider.load_deferred(False)
 
         with solver.use_environment(self._env):
-            ops = solver.solve(use_latest=self._whitelist)
+            ops = solver.solve(use_latest=self._whitelist).calculate_operations(
+                with_uninstalls=self._requires_synchronization,
+                synchronize=self._requires_synchronization,
+            )
+
+        if not self._requires_synchronization:
+            # If no packages synchronisation has been requested we need
+            # to calculate the uninstall operations
+            from conda_lock._vendor.poetry.puzzle.transaction import Transaction
+
+            transaction = Transaction(
+                locked_repository.packages,
+                [(package, 0) for package in lockfile_repo.packages],
+                installed_packages=self._installed_repository.packages,
+                root_package=root,
+            )
+
+            ops = [
+                op
+                for op in transaction.calculate_operations(with_uninstalls=True)
+                if op.job_type == "uninstall"
+            ] + ops
 
         # We need to filter operations so that packages
         # not compatible with the current system,
         # or optional and not requested, are dropped
-        self._filter_operations(ops, local_repo)
+        self._filter_operations(ops, lockfile_repo)
 
         # Execute operations
         return self._execute(ops)
 
-    def _write_lock_file(self, repo, force=True):  # type: (Repository, bool) -> None
-        if force or (self._update and self._write_lock):
+    def _write_lock_file(self, repo: LockfileRepository, force: bool = False) -> None:
+        if self._write_lock and (force or self._update):
             updated_lock = self._locker.set_lock_data(self._package, repo.packages)
 
             if updated_lock:
                 self._io.write_line("")
                 self._io.write_line("Writing lock file")
 
-    def _execute(self, operations):
+    def _execute(self, operations: list[Operation]) -> int:
         if self._use_executor:
             return self._executor.execute(operations)
 
@@ -343,23 +385,13 @@ def _execute(self, operations):
                     uninstalls += 1
 
             self._io.write_line("")
-            self._io.write_line(
-                "Package operations: "
-                "{} install{}, "
-                "{} update{}, "
-                "{} removal{}"
-                "{}".format(
-                    installs,
-                    "" if installs == 1 else "s",
-                    updates,
-                    "" if updates == 1 else "s",
-                    uninstalls,
-                    "" if uninstalls == 1 else "s",
-                    ", {} skipped".format(skipped)
-                    if skipped and self.is_verbose()
-                    else "",
-                )
-            )
+            self._io.write("Package operations: ")
+            self._io.write(f"{installs} install{pluralize(installs)}, ")
+            self._io.write(f"{updates} update{pluralize(updates)}, ")
+            self._io.write(f"{uninstalls} removal{pluralize(uninstalls)}")
+            if skipped and self.is_verbose():
+                self._io.write(f", {skipped} skipped")
+            self._io.write_line("")
 
         self._io.write_line("")
 
@@ -368,32 +400,29 @@ def _execute(self, operations):
 
         return 0
 
-    def _execute_operation(self, operation):  # type: (Operation) -> None
+    def _execute_operation(self, operation: Operation) -> None:
         """
         Execute a given operation.
         """
         method = operation.job_type
 
-        getattr(self, "_execute_{}".format(method))(operation)
+        getattr(self, f"_execute_{method}")(operation)
 
-    def _execute_install(self, operation):  # type: (Install) -> None
+    def _execute_install(self, operation: Install) -> None:
+        target = operation.package
         if operation.skipped:
             if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
                 self._io.write_line(
-                    "  - Skipping {} ({}) {}".format(
-                        operation.package.pretty_name,
-                        operation.package.full_pretty_version,
-                        operation.skip_reason,
-                    )
+                    f"  - Skipping {target.pretty_name}"
+                    f" ({target.full_pretty_version}) {operation.skip_reason}"
                 )
 
             return
 
         if self._execute_operations or self.is_dry_run():
             self._io.write_line(
-                "  - Installing {} ({})".format(
-                    operation.package.pretty_name, operation.package.full_pretty_version
-                )
+                f"  - Installing {target.pretty_name}"
+                f" ({target.full_pretty_version})"
             )
 
         if not self._execute_operations:
@@ -401,29 +430,24 @@ def _execute_install(self, operation):  # type: (Install) -> None
 
         self._installer.install(operation.package)
 
-    def _execute_update(self, operation):  # type: (Update) -> None
+    def _execute_update(self, operation: Update) -> None:
         source = operation.initial_package
         target = operation.target_package
 
         if operation.skipped:
             if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
                 self._io.write_line(
-                    "  - Skipping {} ({}) {}".format(
-                        target.pretty_name,
-                        target.full_pretty_version,
-                        operation.skip_reason,
-                    )
+                    f"  - Skipping {target.pretty_name} "
+                    f"({target.full_pretty_version}) {operation.skip_reason}"
                 )
 
             return
 
         if self._execute_operations or self.is_dry_run():
             self._io.write_line(
-                "  - Updating {} ({} -> {})".format(
-                    target.pretty_name,
-                    source.full_pretty_version,
-                    target.full_pretty_version,
-                )
+                f"  - Updating {target.pretty_name}"
+                f" ({source.full_pretty_version} ->"
+                f" {target.full_pretty_version})"
             )
 
         if not self._execute_operations:
@@ -431,24 +455,21 @@ def _execute_update(self, operation):  # type: (Update) -> None
 
         self._installer.update(source, target)
 
-    def _execute_uninstall(self, operation):  # type: (Uninstall) -> None
+    def _execute_uninstall(self, operation: Uninstall) -> None:
+        target = operation.package
         if operation.skipped:
             if self.is_verbose() and (self._execute_operations or self.is_dry_run()):
                 self._io.write_line(
-                    "  - Not removing {} ({}) {}".format(
-                        operation.package.pretty_name,
-                        operation.package.full_pretty_version,
-                        operation.skip_reason,
-                    )
+                    f"  - Not removing {target.pretty_name}"
+                    f" ({target.pretty_version}) {operation.skip_reason}"
                 )
 
             return
 
         if self._execute_operations or self.is_dry_run():
             self._io.write_line(
-                "  - Removing {} ({})".format(
-                    operation.package.pretty_name, operation.package.full_pretty_version
-                )
+                f"  - Removing {target.pretty_name}"
+                f" ({target.pretty_version})"
             )
 
         if not self._execute_operations:
@@ -456,7 +477,9 @@ def _execute_uninstall(self, operation):  # type: (Uninstall) -> None
 
         self._installer.remove(operation.package)
 
-    def _populate_local_repo(self, local_repo, ops):
+    def _populate_lockfile_repo(
+        self, repo: LockfileRepository, ops: Iterable[Operation]
+    ) -> None:
         for op in ops:
             if isinstance(op, Uninstall):
                 continue
@@ -465,14 +488,14 @@ def _populate_local_repo(self, local_repo, ops):
             else:
                 package = op.package
 
-            if not local_repo.has_package(package):
-                local_repo.add_package(package)
+            if not repo.has_package(package):
+                repo.add_package(package)
 
     def _get_operations_from_lock(
-        self, locked_repository  # type: Repository
-    ):  # type: (...) -> List[Operation]
+        self, locked_repository: Repository
+    ) -> list[Operation]:
         installed_repo = self._installed_repository
-        ops = []
+        ops: list[Operation] = []
 
         extra_packages = self._get_extra_packages(locked_repository)
         for locked in locked_repository.packages:
@@ -480,9 +503,7 @@ def _get_operations_from_lock(
             for installed in installed_repo.packages:
                 if locked.name == installed.name:
                     is_installed = True
-                    if locked.category == "dev" and not self.is_dev_mode():
-                        ops.append(Uninstall(locked))
-                    elif locked.optional and locked.name not in extra_packages:
+                    if locked.optional and locked.name not in extra_packages:
                         # Installed but optional and not requested in extras
                         ops.append(Uninstall(locked))
                     elif locked.version != installed.version:
@@ -501,9 +522,7 @@ def _get_operations_from_lock(
 
         return ops
 
-    def _filter_operations(
-        self, ops, repo
-    ):  # type: (List[Operation], Repository) -> None
+    def _filter_operations(self, ops: Iterable[Operation], repo: Repository) -> None:
         extra_packages = self._get_extra_packages(repo)
         for op in ops:
             if isinstance(op, Update):
@@ -518,41 +537,33 @@ def _filter_operations(
                 op.skip("Not needed for the current environment")
                 continue
 
-            if self._update:
-                extras = {}
-                for extra, deps in self._package.extras.items():
-                    extras[extra] = [dep.name for dep in deps]
-            else:
-                extras = {}
-                for extra, deps in self._locker.lock_data.get("extras", {}).items():
-                    extras[extra] = [dep.lower() for dep in deps]
-
             # If a package is optional and not requested
             # in any extra we skip it
-            if package.optional:
-                if package.name not in extra_packages:
-                    op.skip("Not required")
-
-            # If the package is a dev package and dev packages
-            # are not requested, we skip it
-            if package.category == "dev" and not self.is_dev_mode():
-                op.skip("Dev dependencies not requested")
+            if package.optional and package.name not in extra_packages:
+                op.skip("Not required")
 
-    def _get_extra_packages(self, repo):  # type: (Repository) -> List[str]
+    def _get_extra_packages(self, repo: Repository) -> set[NormalizedName]:
         """
         Returns all package names required by extras.
 
         Maybe we just let the solver handle it?
         """
+        extras: dict[NormalizedName, list[NormalizedName]]
         if self._update:
             extras = {k: [d.name for d in v] for k, v in self._package.extras.items()}
         else:
-            extras = self._locker.lock_data.get("extras", {})
+            raw_extras = self._locker.lock_data.get("extras", {})
+            extras = {
+                canonicalize_name(extra): [
+                    canonicalize_name(dependency) for dependency in dependencies
+                ]
+                for extra, dependencies in raw_extras.items()
+            }
 
-        return list(get_extra_package_names(repo.packages, extras, self._extras))
+        return get_extra_package_names(repo.packages, extras, self._extras)
 
-    def _get_installer(self):  # type: () -> BaseInstaller
+    def _get_installer(self) -> BaseInstaller:
         return PipInstaller(self._env, self._io, self._pool)
 
-    def _get_installed(self):  # type: () -> InstalledRepository
+    def _get_installed(self) -> InstalledRepository:
         return InstalledRepository.load(self._env)
diff --git a/conda_lock/_vendor/poetry/installation/noop_installer.py b/conda_lock/_vendor/poetry/installation/noop_installer.py
index 0f0c6cda0..979a32de8 100644
--- a/conda_lock/_vendor/poetry/installation/noop_installer.py
+++ b/conda_lock/_vendor/poetry/installation/noop_installer.py
@@ -1,29 +1,37 @@
-from .base_installer import BaseInstaller
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.installation.base_installer import BaseInstaller
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
 
 
 class NoopInstaller(BaseInstaller):
-    def __init__(self):
-        self._installs = []
-        self._updates = []
-        self._removals = []
+    def __init__(self) -> None:
+        self._installs: list[Package] = []
+        self._updates: list[tuple[Package, Package]] = []
+        self._removals: list[Package] = []
 
     @property
-    def installs(self):
+    def installs(self) -> list[Package]:
         return self._installs
 
     @property
-    def updates(self):
+    def updates(self) -> list[tuple[Package, Package]]:
         return self._updates
 
     @property
-    def removals(self):
+    def removals(self) -> list[Package]:
         return self._removals
 
-    def install(self, package):
+    def install(self, package: Package) -> None:
         self._installs.append(package)
 
-    def update(self, source, target):
+    def update(self, source: Package, target: Package) -> None:
         self._updates.append((source, target))
 
-    def remove(self, package):
+    def remove(self, package: Package) -> None:
         self._removals.append(package)
diff --git a/conda_lock/_vendor/poetry/installation/operations/__init__.py b/conda_lock/_vendor/poetry/installation/operations/__init__.py
index 42573c10e..04a021582 100644
--- a/conda_lock/_vendor/poetry/installation/operations/__init__.py
+++ b/conda_lock/_vendor/poetry/installation/operations/__init__.py
@@ -1,3 +1,8 @@
-from .install import Install
-from .uninstall import Uninstall
-from .update import Update
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.installation.operations.install import Install
+from conda_lock._vendor.poetry.installation.operations.uninstall import Uninstall
+from conda_lock._vendor.poetry.installation.operations.update import Update
+
+
+__all__ = ["Install", "Uninstall", "Update"]
diff --git a/conda_lock/_vendor/poetry/installation/operations/install.py b/conda_lock/_vendor/poetry/installation/operations/install.py
index 48097c7c6..439ec872a 100644
--- a/conda_lock/_vendor/poetry/installation/operations/install.py
+++ b/conda_lock/_vendor/poetry/installation/operations/install.py
@@ -1,26 +1,38 @@
-from .operation import Operation
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.installation.operations.operation import Operation
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
 
 
 class Install(Operation):
-    def __init__(self, package, reason=None, priority=0):
-        super(Install, self).__init__(reason, priority=priority)
+    def __init__(
+        self, package: Package, reason: str | None = None, priority: int = 0
+    ) -> None:
+        super().__init__(reason, priority=priority)
 
         self._package = package
 
     @property
-    def package(self):
+    def package(self) -> Package:
         return self._package
 
     @property
-    def job_type(self):
+    def job_type(self) -> str:
         return "install"
 
-    def __str__(self):
-        return "Installing {} ({})".format(
-            self.package.pretty_name, self.format_version(self.package)
+    def __str__(self) -> str:
+        return (
+            "Installing"
+            f" {self.package.pretty_name} ({self.format_version(self.package)})"
         )
 
-    def __repr__(self):
-        return "".format(
-            self.package.pretty_name, self.format_version(self.package)
+    def __repr__(self) -> str:
+        return (
+            ""
         )
diff --git a/conda_lock/_vendor/poetry/installation/operations/operation.py b/conda_lock/_vendor/poetry/installation/operations/operation.py
index 0c72cc8c0..d98e64df3 100644
--- a/conda_lock/_vendor/poetry/installation/operations/operation.py
+++ b/conda_lock/_vendor/poetry/installation/operations/operation.py
@@ -1,52 +1,58 @@
-# -*- coding: utf-8 -*-
+from __future__ import annotations
 
-from typing import Union
+from typing import TYPE_CHECKING
+from typing import TypeVar
 
 
-class Operation(object):
-    def __init__(
-        self, reason=None, priority=0
-    ):  # type: (Union[str, None], int) -> None
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+T = TypeVar("T", bound="Operation")
+
+
+class Operation:
+    def __init__(self, reason: str | None = None, priority: int | float = 0) -> None:
         self._reason = reason
 
         self._skipped = False
-        self._skip_reason = None
+        self._skip_reason: str | None = None
         self._priority = priority
 
     @property
-    def job_type(self):  # type: () -> str
+    def job_type(self) -> str:
         raise NotImplementedError
 
     @property
-    def reason(self):  # type: () -> str
+    def reason(self) -> str | None:
         return self._reason
 
     @property
-    def skipped(self):  # type: () -> bool
+    def skipped(self) -> bool:
         return self._skipped
 
     @property
-    def skip_reason(self):  # type: () -> Union[str, None]
+    def skip_reason(self) -> str | None:
         return self._skip_reason
 
     @property
-    def priority(self):  # type: () -> int
+    def priority(self) -> float | int:
         return self._priority
 
     @property
-    def package(self):
+    def package(self) -> Package:
         raise NotImplementedError()
 
-    def format_version(self, package):  # type: (...) -> str
-        return package.full_pretty_version
+    def format_version(self, package: Package) -> str:
+        version: str = package.full_pretty_version
+        return version
 
-    def skip(self, reason):  # type: (str) -> Operation
+    def skip(self: T, reason: str) -> T:
         self._skipped = True
         self._skip_reason = reason
 
         return self
 
-    def unskip(self):  # type: () -> Operation
+    def unskip(self: T) -> T:
         self._skipped = False
         self._skip_reason = None
 
diff --git a/conda_lock/_vendor/poetry/installation/operations/uninstall.py b/conda_lock/_vendor/poetry/installation/operations/uninstall.py
index b7e40bc60..d3bd08401 100644
--- a/conda_lock/_vendor/poetry/installation/operations/uninstall.py
+++ b/conda_lock/_vendor/poetry/installation/operations/uninstall.py
@@ -1,26 +1,41 @@
-from .operation import Operation
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.installation.operations.operation import Operation
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
 
 
 class Uninstall(Operation):
-    def __init__(self, package, reason=None, priority=float("inf")):
-        super(Uninstall, self).__init__(reason, priority=priority)
+    def __init__(
+        self,
+        package: Package,
+        reason: str | None = None,
+        priority: float | int = float("inf"),
+    ) -> None:
+        super().__init__(reason, priority=priority)
 
         self._package = package
 
     @property
-    def package(self):
+    def package(self) -> Package:
         return self._package
 
     @property
-    def job_type(self):
+    def job_type(self) -> str:
         return "uninstall"
 
-    def __str__(self):
-        return "Uninstalling {} ({})".format(
-            self.package.pretty_name, self.format_version(self._package)
+    def __str__(self) -> str:
+        return (
+            "Uninstalling"
+            f" {self.package.pretty_name} ({self.format_version(self._package)})"
         )
 
-    def __repr__(self):
-        return "".format(
-            self.package.pretty_name, self.format_version(self.package)
+    def __repr__(self) -> str:
+        return (
+            ""
         )
diff --git a/conda_lock/_vendor/poetry/installation/operations/update.py b/conda_lock/_vendor/poetry/installation/operations/update.py
index 87803fd7a..1f45c78d0 100644
--- a/conda_lock/_vendor/poetry/installation/operations/update.py
+++ b/conda_lock/_vendor/poetry/installation/operations/update.py
@@ -1,41 +1,55 @@
-from .operation import Operation
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.installation.operations.operation import Operation
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
 
 
 class Update(Operation):
-    def __init__(self, initial, target, reason=None, priority=0):
+    def __init__(
+        self,
+        initial: Package,
+        target: Package,
+        reason: str | None = None,
+        priority: int = 0,
+    ) -> None:
         self._initial_package = initial
         self._target_package = target
 
-        super(Update, self).__init__(reason, priority=priority)
+        super().__init__(reason, priority=priority)
 
     @property
-    def initial_package(self):
+    def initial_package(self) -> Package:
         return self._initial_package
 
     @property
-    def target_package(self):
+    def target_package(self) -> Package:
         return self._target_package
 
     @property
-    def package(self):
+    def package(self) -> Package:
         return self._target_package
 
     @property
-    def job_type(self):
+    def job_type(self) -> str:
         return "update"
 
-    def __str__(self):
-        return "Updating {} ({}) to {} ({})".format(
-            self.initial_package.pretty_name,
-            self.format_version(self.initial_package),
-            self.target_package.pretty_name,
-            self.format_version(self.target_package),
+    def __str__(self) -> str:
+        init_version = self.format_version(self.initial_package)
+        target_version = self.format_version(self.target_package)
+        return (
+            f"Updating {self.initial_package.pretty_name} ({init_version}) "
+            f"to {self.target_package.pretty_name} ({target_version})"
         )
 
-    def __repr__(self):
-        return "".format(
-            self.initial_package.pretty_name,
-            self.format_version(self.initial_package),
-            self.target_package.pretty_name,
-            self.format_version(self.target_package),
+    def __repr__(self) -> str:
+        init_version = self.format_version(self.initial_package)
+        target_version = self.format_version(self.target_package)
+        return (
+            f""
         )
diff --git a/conda_lock/_vendor/poetry/installation/pip_installer.py b/conda_lock/_vendor/poetry/installation/pip_installer.py
index 919520c6b..c9b3f903e 100644
--- a/conda_lock/_vendor/poetry/installation/pip_installer.py
+++ b/conda_lock/_vendor/poetry/installation/pip_installer.py
@@ -1,32 +1,41 @@
+from __future__ import annotations
+
+import contextlib
 import os
 import tempfile
+import urllib.parse
 
+from pathlib import Path
 from subprocess import CalledProcessError
+from typing import TYPE_CHECKING
+from typing import Any
 
-from clikit.api.io import IO
-
+from conda_lock._vendor.poetry.core.constraints.version import Version
 from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML
-from conda_lock._vendor.poetry.repositories.pool import Pool
+
+from conda_lock._vendor.poetry.installation.base_installer import BaseInstaller
+from conda_lock._vendor.poetry.repositories.http_repository import HTTPRepository
 from conda_lock._vendor.poetry.utils._compat import encode
-from conda_lock._vendor.poetry.utils.env import Env
-from conda_lock._vendor.poetry.utils.helpers import safe_rmtree
+from conda_lock._vendor.poetry.utils.helpers import remove_directory
+from conda_lock._vendor.poetry.utils.pip import pip_install
 
-from .base_installer import BaseInstaller
 
+if TYPE_CHECKING:
+    from conda_lock._vendor.cleo.io.io import IO
+    from conda_lock._vendor.poetry.core.masonry.builders.builder import Builder
+    from conda_lock._vendor.poetry.core.packages.package import Package
 
-try:
-    import urllib.parse as urlparse
-except ImportError:
-    import urlparse
+    from conda_lock._vendor.poetry.repositories.repository_pool import RepositoryPool
+    from conda_lock._vendor.poetry.utils.env import Env
 
 
 class PipInstaller(BaseInstaller):
-    def __init__(self, env, io, pool):  # type: (Env, IO, Pool) -> None
+    def __init__(self, env: Env, io: IO, pool: RepositoryPool) -> None:
         self._env = env
         self._io = io
         self._pool = pool
 
-    def install(self, package, update=False):
+    def install(self, package: Package, update: bool = False) -> None:
         if package.source_type == "directory":
             self.install_directory(package)
 
@@ -37,41 +46,53 @@ def install(self, package, update=False):
 
             return
 
-        args = ["install", "--no-deps"]
+        args = ["install", "--no-deps", "--no-input"]
 
-        if (
-            package.source_type not in {"git", "directory", "file", "url"}
-            and package.source_url
-        ):
+        if not package.is_direct_origin() and package.source_url:
+            assert package.source_reference is not None
             repository = self._pool.repository(package.source_reference)
-            parsed = urlparse.urlparse(package.source_url)
+            parsed = urllib.parse.urlparse(package.source_url)
             if parsed.scheme == "http":
-                self._io.error(
-                    "    Installing from unsecure host: {}".format(
-                        parsed.hostname
-                    )
+                assert parsed.hostname is not None
+                self._io.write_error(
+                    "    Installing from unsecure host:"
+                    f" {parsed.hostname}"
                 )
                 args += ["--trusted-host", parsed.hostname]
 
-            if repository.cert:
-                args += ["--cert", str(repository.cert)]
+            if isinstance(repository, HTTPRepository):
+                certificates = repository.certificates
+
+                if certificates.cert:
+                    args += ["--cert", str(certificates.cert)]
+
+                if parsed.scheme == "https" and not certificates.verify:
+                    assert parsed.hostname is not None
+                    args += ["--trusted-host", parsed.hostname]
+
+                if certificates.client_cert:
+                    args += ["--client-cert", str(certificates.client_cert)]
+
+                index_url = repository.authenticated_url
 
-            if repository.client_cert:
-                args += ["--client-cert", str(repository.client_cert)]
+                args += ["--index-url", index_url]
 
-            index_url = repository.authenticated_url
+            if (
+                self._pool.has_default()
+                and repository.name != self._pool.repositories[0].name
+            ):
+                first_repository = self._pool.repositories[0]
 
-            args += ["--index-url", index_url]
-            if self._pool.has_default():
-                if repository.name != self._pool.repositories[0].name:
+                if isinstance(first_repository, HTTPRepository):
                     args += [
                         "--extra-index-url",
-                        self._pool.repositories[0].authenticated_url,
+                        first_repository.authenticated_url,
                     ]
 
         if update:
             args.append("-U")
 
+        req: str | list[str]
         if package.files and not package.source_url:
             # Format as a requirements.txt
             # We need to create a requirements.txt file
@@ -95,7 +116,7 @@ def install(self, package, update=False):
 
             self.run(*args)
 
-    def update(self, package, target):
+    def update(self, package: Package, target: Package) -> None:
         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
@@ -103,7 +124,7 @@ def update(self, package, target):
 
         self.install(target, update=True)
 
-    def remove(self, package):
+    def remove(self, package: Package) -> None:
         try:
             self.run("uninstall", package.name, "-y")
         except CalledProcessError as e:
@@ -113,159 +134,159 @@ def remove(self, package):
             raise
 
         # This is a workaround for https://github.com/pypa/pip/issues/4176
-        nspkg_pth_file = self._env.site_packages.path / "{}-nspkg.pth".format(
-            package.name
-        )
-        if nspkg_pth_file.exists():
+        for nspkg_pth_file in self._env.site_packages.find_distribution_nspkg_pth_files(
+            distribution_name=package.name
+        ):
             nspkg_pth_file.unlink()
 
         # If we have a VCS package, remove its source directory
         if package.source_type == "git":
             src_dir = self._env.path / "src" / package.name
             if src_dir.exists():
-                safe_rmtree(str(src_dir))
+                remove_directory(src_dir, force=True)
 
-    def run(self, *args, **kwargs):  # type: (...) -> str
+    def run(self, *args: Any, **kwargs: Any) -> int | str:
         return self._env.run_pip(*args, **kwargs)
 
-    def requirement(self, package, formatted=False):
+    def requirement(self, package: Package, formatted: bool = False) -> str | list[str]:
         if formatted and not package.source_type:
-            req = "{}=={}".format(package.name, package.version)
+            req = f"{package.name}=={package.version}"
             for f in package.files:
                 hash_type = "sha256"
                 h = f["hash"]
                 if ":" in h:
                     hash_type, h = h.split(":")
 
-                req += " --hash {}:{}".format(hash_type, h)
+                req += f" --hash {hash_type}:{h}"
 
             req += "\n"
 
             return req
 
         if package.source_type in ["file", "directory"]:
+            assert package.source_url is not None
             if package.root_dir:
                 req = (package.root_dir / package.source_url).as_posix()
             else:
                 req = os.path.realpath(package.source_url)
 
             if package.develop and package.source_type == "directory":
-                req = ["-e", req]
+                return ["-e", req]
 
             return req
 
         if package.source_type == "git":
-            req = "git+{}@{}#egg={}".format(
-                package.source_url, package.source_reference, package.name
+            req = (
+                f"git+{package.source_url}@{package.source_reference}"
+                f"#egg={package.name}"
             )
 
+            if package.source_subdirectory:
+                req += f"&subdirectory={package.source_subdirectory}"
+
             if package.develop:
-                req = ["-e", req]
+                return ["-e", req]
 
             return req
 
         if package.source_type == "url":
-            return "{}#egg={}".format(package.source_url, package.name)
+            return f"{package.source_url}#egg={package.name}"
 
-        return "{}=={}".format(package.name, package.version)
+        return f"{package.name}=={package.version}"
 
-    def create_temporary_requirement(self, package):
-        fd, name = tempfile.mkstemp(
-            "reqs.txt", "{}-{}".format(package.name, package.version)
-        )
+    def create_temporary_requirement(self, package: Package) -> str:
+        fd, name = tempfile.mkstemp("reqs.txt", f"{package.name}-{package.version}")
+        req = self.requirement(package, formatted=True)
+        if isinstance(req, list):
+            req = " ".join(req)
 
         try:
-            os.write(fd, encode(self.requirement(package, formatted=True)))
+            os.write(fd, encode(req))
         finally:
             os.close(fd)
 
         return name
 
-    def install_directory(self, package):
+    def install_directory(self, package: Package) -> str | int:
+        from conda_lock._vendor.cleo.io.null_io import NullIO
+
         from conda_lock._vendor.poetry.factory import Factory
-        from conda_lock._vendor.poetry.io.null_io import NullIO
 
+        assert package.source_url is not None
         if package.root_dir:
-            req = (package.root_dir / package.source_url).as_posix()
+            req = package.root_dir / package.source_url
         else:
-            req = os.path.realpath(package.source_url)
+            req = Path(package.source_url).resolve(strict=False)
 
-        args = ["install", "--no-deps", "-U"]
+        if package.source_subdirectory:
+            req /= package.source_subdirectory
 
         pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))
 
+        package_poetry = None
         if pyproject.is_poetry_project():
+            with contextlib.suppress(RuntimeError):
+                package_poetry = Factory().create_poetry(pyproject.file.path.parent)
+
+        if package_poetry is not None:
             # Even if there is a build system specified
             # some versions of pip (< 19.0.0) don't understand it
             # so we need to check the version of pip to know
             # if we can rely on the build system
-            legacy_pip = self._env.pip_version < self._env.pip_version.__class__(
-                19, 0, 0
-            )
-
-            try:
-                package_poetry = Factory().create_poetry(pyproject.file.path.parent)
-            except RuntimeError:
-                package_poetry = None
-
-            if package_poetry is not None:
-                if package.develop and not package_poetry.package.build_script:
-                    from conda_lock._vendor.poetry.masonry.builders.editable import EditableBuilder
-
-                    # This is a Poetry package in editable mode
-                    # we can use the EditableBuilder without going through pip
-                    # to install it, unless it has a build script.
-                    builder = EditableBuilder(package_poetry, self._env, NullIO())
-                    builder.build()
-
-                    return 0
-                elif legacy_pip or package_poetry.package.build_script:
-                    from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder
-
-                    # We need to rely on creating a temporary setup.py
-                    # file since the version of pip does not support
-                    # build-systems
-                    # We also need it for non-PEP-517 packages
-                    builder = SdistBuilder(package_poetry)
-
-                    with builder.setup_py():
-                        if package.develop:
-                            args.append("-e")
-
-                        args.append(req)
-
-                        return self.run(*args)
-
-        if package.develop:
-            args.append("-e")
-
-        args.append(req)
-
-        return self.run(*args)
-
-    def install_git(self, package):
-        from conda_lock._vendor.poetry.core.packages import Package
-        from conda_lock._vendor.poetry.core.vcs import Git
-
-        src_dir = self._env.path / "src" / package.name
-        if src_dir.exists():
-            safe_rmtree(str(src_dir))
+            legacy_pip = self._env.pip_version < Version.from_parts(19, 0, 0)
+
+            builder: Builder
+            if package.develop and not package_poetry.package.build_script:
+                from conda_lock._vendor.poetry.masonry.builders.editable import EditableBuilder
+
+                # This is a Poetry package in editable mode
+                # we can use the EditableBuilder without going through pip
+                # to install it, unless it has a build script.
+                builder = EditableBuilder(package_poetry, self._env, NullIO())
+                builder.build()
+
+                return 0
+            elif legacy_pip or package_poetry.package.build_script:
+                from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder
+
+                # We need to rely on creating a temporary setup.py
+                # file since the version of pip does not support
+                # build-systems
+                # We also need it for non-PEP-517 packages
+                builder = SdistBuilder(package_poetry)
+
+                with builder.setup_py():
+                    return pip_install(
+                        path=req,
+                        environment=self._env,
+                        upgrade=True,
+                        editable=package.develop,
+                    )
 
-        src_dir.parent.mkdir(exist_ok=True)
+        return pip_install(
+            path=req, environment=self._env, upgrade=True, editable=package.develop
+        )
 
-        git = Git()
-        git.clone(package.source_url, src_dir)
+    def install_git(self, package: Package) -> None:
+        from conda_lock._vendor.poetry.core.packages.package import Package
 
-        reference = package.source_resolved_reference
-        if not reference:
-            reference = package.source_reference
+        from conda_lock._vendor.poetry.vcs.git import Git
 
-        git.checkout(reference, src_dir)
+        assert package.source_url is not None
+        source = Git.clone(
+            url=package.source_url,
+            source_root=self._env.path / "src",
+            revision=package.source_resolved_reference or package.source_reference,
+        )
 
         # Now we just need to install from the source directory
-        pkg = Package(package.name, package.version)
-        pkg._source_type = "directory"
-        pkg._source_url = str(src_dir)
-        pkg.develop = package.develop
+        pkg = Package(
+            name=package.name,
+            version=package.version,
+            source_type="directory",
+            source_url=str(source.path),
+            source_subdirectory=package.source_subdirectory,
+            develop=package.develop,
+        )
 
         self.install_directory(pkg)
diff --git a/conda_lock/_vendor/poetry/io/null_io.py b/conda_lock/_vendor/poetry/io/null_io.py
deleted file mode 100644
index 1acab4eaf..000000000
--- a/conda_lock/_vendor/poetry/io/null_io.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from conda_lock._vendor.cleo.io.io_mixin import IOMixin
-from clikit.io import NullIO as BaseNullIO
-
-
-class NullIO(IOMixin, BaseNullIO):
-    """
-    A wrapper around CliKit's NullIO.
-    """
-
-    def __init__(self, *args, **kwargs):
-        super(NullIO, self).__init__(*args, **kwargs)
diff --git a/conda_lock/_vendor/poetry/json/__init__.py b/conda_lock/_vendor/poetry/json/__init__.py
index d50eb7a75..e9ba8ba09 100644
--- a/conda_lock/_vendor/poetry/json/__init__.py
+++ b/conda_lock/_vendor/poetry/json/__init__.py
@@ -1,41 +1,54 @@
+from __future__ import annotations
+
 import json
 import os
 
-from io import open
-from typing import List
+from pathlib import Path
+from typing import Any
 
 import jsonschema
 
+from conda_lock._vendor.poetry.core.json import SCHEMA_DIR as CORE_SCHEMA_DIR
+
 
 SCHEMA_DIR = os.path.join(os.path.dirname(__file__), "schemas")
 
 
 class ValidationError(ValueError):
-
     pass
 
 
-def validate_object(obj, schema_name):  # type: (dict, str) -> List[str]
-    schema = os.path.join(SCHEMA_DIR, "{}.json".format(schema_name))
-
-    if not os.path.exists(schema):
-        raise ValueError("Schema {} does not exist.".format(schema_name))
-
-    with open(schema, encoding="utf-8") as f:
-        schema = json.loads(f.read())
+def validate_object(obj: dict[str, Any]) -> list[str]:
+    schema_file = Path(SCHEMA_DIR, "poetry.json")
+    schema = json.loads(schema_file.read_text(encoding="utf-8"))
 
     validator = jsonschema.Draft7Validator(schema)
-    validation_errors = sorted(validator.iter_errors(obj), key=lambda e: e.path)
+    validation_errors = sorted(
+        validator.iter_errors(obj),
+        key=lambda e: e.path,  # type: ignore[no-any-return]
+    )
 
     errors = []
 
     for error in validation_errors:
         message = error.message
         if error.path:
-            message = "[{}] {}".format(
-                ".".join(str(x) for x in error.absolute_path), message
-            )
+            path = ".".join(str(x) for x in error.absolute_path)
+            message = f"[{path}] {message}"
 
         errors.append(message)
 
+    core_schema = json.loads(
+        Path(CORE_SCHEMA_DIR, "poetry-schema.json").read_text(encoding="utf-8")
+    )
+
+    if core_schema["additionalProperties"]:
+        # TODO: make this un-conditional once core update to >1.1.0b2
+        properties = {*schema["properties"].keys(), *core_schema["properties"].keys()}
+        additional_properties = set(obj.keys()) - properties
+        for key in additional_properties:
+            errors.append(
+                f"Additional properties are not allowed ('{key}' was unexpected)"
+            )
+
     return errors
diff --git a/conda_lock/_vendor/poetry/json/schemas/poetry-schema.json b/conda_lock/_vendor/poetry/json/schemas/poetry-schema.json
deleted file mode 100644
index e94b90d28..000000000
--- a/conda_lock/_vendor/poetry/json/schemas/poetry-schema.json
+++ /dev/null
@@ -1,530 +0,0 @@
-{
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "name": "Package",
-    "type": "object",
-    "additionalProperties": false,
-    "required": [
-        "name",
-        "version",
-        "description"
-    ],
-    "properties": {
-        "name": {
-            "type": "string",
-            "description": "Package name."
-        },
-        "version": {
-            "type": "string",
-            "description": "Package version."
-        },
-        "description": {
-            "type": "string",
-            "description": "Short package description."
-        },
-        "keywords": {
-            "type": "array",
-            "items": {
-                "type": "string",
-                "description": "A tag/keyword that this package relates to."
-            }
-        },
-        "homepage": {
-            "type": "string",
-            "description": "Homepage URL for the project.",
-            "format": "uri"
-        },
-        "repository": {
-            "type": "string",
-            "description": "Repository URL for the project.",
-            "format": "uri"
-        },
-        "documentation": {
-            "type": "string",
-            "description": "Documentation URL for the project.",
-            "format": "uri"
-        },
-        "license": {
-            "type": "string",
-            "description": "License name."
-        },
-        "authors": {
-            "$ref": "#/definitions/authors"
-        },
-        "maintainers": {
-            "$ref": "#/definitions/maintainers"
-        },
-        "readme": {
-            "type": "string",
-            "description": "The path to the README file"
-        },
-        "classifiers": {
-            "type": "array",
-            "description": "A list of trove classifers."
-        },
-        "packages": {
-            "type": "array",
-            "description": "A list of packages to include in the final distribution.",
-            "items": {
-                "type": "object",
-                "description": "Information about where the package resides.",
-                "additionalProperties": false,
-                "required": [
-                    "include"
-                ],
-                "properties": {
-                    "include": {
-                        "type": "string",
-                        "description": "What to include in the package."
-                    },
-                    "from": {
-                        "type": "string",
-                        "description": "Where the source directory of the package resides."
-                    },
-                    "format": {
-                        "oneOf": [
-                            {"type": "string"},
-                            {"type":  "array", "items": {"type":  "string"}}
-                        ],
-                        "description": "The format(s) for which the package must be included."
-                    }
-                }
-            }
-        },
-        "include": {
-            "type": "array",
-            "description": "A list of files and folders to include."
-        },
-        "exclude": {
-            "type": "array",
-            "description": "A list of files and folders to exclude."
-        },
-        "dependencies": {
-            "type": "object",
-            "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.",
-            "required": [
-                "python"
-            ],
-            "properties": {
-                "python": {
-                    "type": "string",
-                    "description": "The Python versions the package is compatible with."
-                }
-            },
-            "$ref": "#/definitions/dependencies",
-            "additionalProperties": false
-        },
-        "dev-dependencies": {
-            "type": "object",
-            "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
-            "$ref": "#/definitions/dependencies",
-            "additionalProperties": false
-        },
-        "extras": {
-            "type": "object",
-            "patternProperties": {
-                "^[a-zA-Z-_.0-9]+$": {
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
-                }
-            }
-        },
-        "build": {
-            "type": "string",
-            "description": "The file used to build extensions."
-        },
-        "source": {
-            "type": "array",
-            "description": "A set of additional repositories where packages can be found.",
-            "additionalProperties": {
-                "$ref": "#/definitions/repository"
-            },
-            "items": {
-                "$ref": "#/definitions/repository"
-            }
-        },
-        "scripts": {
-            "type": "object",
-            "description": "A hash of scripts to be installed.",
-            "items": {
-                "type": "string"
-            }
-        },
-        "plugins": {
-            "type": "object",
-            "description": "A hash of hashes representing plugins",
-            "patternProperties": {
-                "^[a-zA-Z-_.0-9]+$": {
-                    "type": "object",
-                    "patternProperties": {
-                        "^[a-zA-Z-_.0-9]+$": {
-                            "type": "string"
-                        }
-                    }
-                }
-            }
-        },
-        "urls": {
-            "type": "object",
-            "patternProperties": {
-                "^.+$": {
-                    "type": "string",
-                    "description": "The full url of the custom url."
-                }
-            }
-        }
-    },
-    "definitions": {
-        "authors": {
-            "type": "array",
-            "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.",
-            "items": {
-                "type": "string"
-            }
-        },
-        "maintainers": {
-            "type": "array",
-            "description": "List of maintainers, other than the original author(s), that upkeep the package.",
-            "items": {
-                "type": "string"
-            }
-        },
-        "dependencies": {
-            "type": "object",
-            "patternProperties": {
-                "^[a-zA-Z-_.0-9]+$": {
-                    "oneOf": [
-                        {
-                            "$ref": "#/definitions/dependency"
-                        },
-                        {
-                            "$ref": "#/definitions/long-dependency"
-                        },
-                        {
-                            "$ref": "#/definitions/git-dependency"
-                        },
-                        {
-                            "$ref": "#/definitions/file-dependency"
-                        },
-                        {
-                            "$ref": "#/definitions/path-dependency"
-                        },
-                        {
-                            "$ref": "#/definitions/url-dependency"
-                        },
-                        {
-                            "$ref": "#/definitions/multiple-constraints-dependency"
-                        }
-                    ]
-                }
-            }
-        },
-        "dependency": {
-            "type": "string",
-            "description": "The constraint of the dependency."
-        },
-        "long-dependency": {
-            "type": "object",
-            "required": [
-                "version"
-            ],
-            "additionalProperties": false,
-            "properties": {
-                "version": {
-                    "type": "string",
-                    "description": "The constraint of the dependency."
-                },
-                "python": {
-                    "type": "string",
-                    "description": "The python versions for which the dependency should be installed."
-                },
-                "platform": {
-                    "type": "string",
-                    "description": "The platform(s) for which the dependency should be installed."
-                },
-                "markers": {
-                    "type": "string",
-                    "description": "The PEP 508 compliant environment markers for which the dependency should be installed."
-                },
-                "allow-prereleases": {
-                    "type": "boolean",
-                    "description": "Whether the dependency allows prereleases or not."
-                },
-                "allows-prereleases": {
-                    "type": "boolean",
-                    "description": "Whether the dependency allows prereleases or not."
-                },
-                "optional": {
-                    "type": "boolean",
-                    "description": "Whether the dependency is optional or not."
-                },
-                "extras": {
-                    "type": "array",
-                    "description": "The required extras for this dependency.",
-                    "items": {
-                        "type": "string"
-                    }
-                },
-                "source": {
-                    "type": "string",
-                    "description": "The exclusive source used to search for this dependency."
-                }
-            }
-        },
-        "git-dependency": {
-            "type": "object",
-            "required": [
-                "git"
-            ],
-            "additionalProperties": false,
-            "properties": {
-                "git": {
-                    "type": "string",
-                    "description": "The url of the git repository.",
-                    "format": "uri"
-                },
-                "branch": {
-                    "type": "string",
-                    "description": "The branch to checkout."
-                },
-                "tag": {
-                    "type": "string",
-                    "description": "The tag to checkout."
-                },
-                "rev": {
-                    "type": "string",
-                    "description": "The revision to checkout."
-                },
-                "python": {
-                    "type": "string",
-                    "description": "The python versions for which the dependency should be installed."
-                },
-                "platform": {
-                    "type": "string",
-                    "description": "The platform(s) for which the dependency should be installed."
-                },
-                "markers": {
-                    "type": "string",
-                    "description": "The PEP 508 compliant environment markers for which the dependency should be installed."
-                },
-                "allow-prereleases": {
-                    "type": "boolean",
-                    "description": "Whether the dependency allows prereleases or not."
-                },
-                "allows-prereleases": {
-                    "type": "boolean",
-                    "description": "Whether the dependency allows prereleases or not."
-                },
-                "optional": {
-                    "type": "boolean",
-                    "description": "Whether the dependency is optional or not."
-                },
-                "extras": {
-                    "type": "array",
-                    "description": "The required extras for this dependency.",
-                    "items": {
-                        "type": "string"
-                    }
-                }
-            }
-        },
-        "file-dependency": {
-            "type": "object",
-            "required": [
-                "file"
-            ],
-            "additionalProperties": false,
-            "properties": {
-                "file": {
-                    "type": "string",
-                    "description": "The path to the file."
-                },
-                "python": {
-                    "type": "string",
-                    "description": "The python versions for which the dependency should be installed."
-                },
-                "platform": {
-                    "type": "string",
-                    "description": "The platform(s) for which the dependency should be installed."
-                },
-                "markers": {
-                    "type": "string",
-                    "description": "The PEP 508 compliant environment markers for which the dependency should be installed."
-                },
-                "optional": {
-                    "type": "boolean",
-                    "description": "Whether the dependency is optional or not."
-                },
-                "extras": {
-                    "type": "array",
-                    "description": "The required extras for this dependency.",
-                    "items": {
-                        "type": "string"
-                    }
-                }
-            }
-        },
-        "path-dependency": {
-            "type": "object",
-            "required": [
-                "path"
-            ],
-            "additionalProperties": false,
-            "properties": {
-                "path": {
-                    "type": "string",
-                    "description": "The path to the dependency."
-                },
-                "python": {
-                    "type": "string",
-                    "description": "The python versions for which the dependency should be installed."
-                },
-                "platform": {
-                    "type": "string",
-                    "description": "The platform(s) for which the dependency should be installed."
-                },
-                "markers": {
-                    "type": "string",
-                    "description": "The PEP 508 compliant environment markers for which the dependency should be installed."
-                },
-                "optional": {
-                    "type": "boolean",
-                    "description": "Whether the dependency is optional or not."
-                },
-                "extras": {
-                    "type": "array",
-                    "description": "The required extras for this dependency.",
-                    "items": {
-                        "type": "string"
-                    }
-                },
-                "develop": {
-                    "type": "boolean",
-                    "description": "Whether to install the dependency in development mode."
-                }
-            }
-        },
-        "url-dependency": {
-            "type": "object",
-            "required": [
-                "url"
-            ],
-            "additionalProperties": false,
-            "properties": {
-                "url": {
-                    "type": "string",
-                    "description": "The url to the file."
-                },
-                "python": {
-                    "type": "string",
-                    "description": "The python versions for which the dependency should be installed."
-                },
-                "platform": {
-                    "type": "string",
-                    "description": "The platform(s) for which the dependency should be installed."
-                },
-                "markers": {
-                    "type": "string",
-                    "description": "The PEP 508 compliant environment markers for which the dependency should be installed."
-                },
-                "optional": {
-                    "type": "boolean",
-                    "description": "Whether the dependency is optional or not."
-                },
-                "extras": {
-                    "type": "array",
-                    "description": "The required extras for this dependency.",
-                    "items": {
-                        "type": "string"
-                    }
-                }
-            }
-        },
-        "multiple-constraints-dependency": {
-            "type": "array",
-            "minItems": 1,
-            "items": {
-                "oneOf": [
-                    {
-                        "$ref": "#/definitions/dependency"
-                    },
-                    {
-                        "$ref": "#/definitions/long-dependency"
-                    },
-                    {
-                        "$ref": "#/definitions/git-dependency"
-                    },
-                    {
-                        "$ref": "#/definitions/file-dependency"
-                    },
-                    {
-                        "$ref": "#/definitions/path-dependency"
-                    },
-                    {
-                        "$ref": "#/definitions/url-dependency"
-                    }
-                ]
-            }
-        },
-        "scripts": {
-            "type": "object",
-            "patternProperties": {
-                "^[a-zA-Z-_.0-9]+$": {
-                    "oneOf": [
-                        {
-                            "$ref": "#/definitions/script"
-                        },
-                        {
-                            "$ref": "#/definitions/extra-script"
-                        }
-                    ]
-                }
-            }
-        },
-        "script": {
-            "type": "string",
-            "description": "A simple script pointing to a callable object."
-        },
-        "extra-script": {
-            "type": "object",
-            "description": "A script that should be installed only if extras are activated.",
-            "additionalProperties": false,
-            "properties": {
-                "callable": {
-                    "$ref": "#/definitions/script"
-                },
-                "extras": {
-                    "type": "array",
-                    "description": "The required extras for this script.",
-                    "items": {
-                        "type": "string"
-                    }
-                }
-            }
-        },
-        "repository": {
-            "type": "object",
-            "additionalProperties": false,
-            "properties": {
-                "name": {
-                    "type": "string",
-                    "description": "The name of the repository"
-                },
-                "url": {
-                    "type": "string",
-                    "description": "The url of the repository",
-                    "format": "uri"
-                },
-                "default": {
-                    "type": "boolean",
-                    "description": "Make this repository the default (disable PyPI)"
-                },
-                "secondary": {
-                    "type": "boolean",
-                    "description": "Declare this repository as secondary, i.e. it will only be looked up last for packages."
-                }
-            }
-        }
-    }
-}
diff --git a/conda_lock/_vendor/poetry/json/schemas/poetry.json b/conda_lock/_vendor/poetry/json/schemas/poetry.json
new file mode 100644
index 000000000..7532fd836
--- /dev/null
+++ b/conda_lock/_vendor/poetry/json/schemas/poetry.json
@@ -0,0 +1,55 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "additionalProperties": true,
+  "type": "object",
+  "required": [],
+  "properties": {
+    "source": {
+      "type": "array",
+      "description": "A set of additional repositories where packages can be found.",
+      "additionalProperties": {
+        "$ref": "#/definitions/repository"
+      },
+      "items": {
+        "$ref": "#/definitions/repository"
+      }
+    }
+  },
+  "definitions": {
+    "repository": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "name",
+        "url"
+      ],
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the repository"
+        },
+        "url": {
+          "type": "string",
+          "description": "The url of the repository",
+          "format": "uri"
+        },
+        "default": {
+          "type": "boolean",
+          "description": "Make this repository the default (disable PyPI)"
+        },
+        "secondary": {
+          "type": "boolean",
+          "description": "Declare this repository as secondary, i.e. it will only be looked up last for packages."
+        },
+        "links": {
+          "type": "boolean",
+          "description": "Declare this as a link source. Links at uri/path can point to sdist or bdist archives."
+        },
+        "indexed": {
+          "type": "boolean",
+          "description": "For PEP 503 simple API repositories, pre-fetch and index the available packages. (experimental)"
+        }
+      }
+    }
+  }
+}
diff --git a/conda_lock/_vendor/poetry/layouts/__init__.py b/conda_lock/_vendor/poetry/layouts/__init__.py
index 9969ce5e3..0227ed72b 100644
--- a/conda_lock/_vendor/poetry/layouts/__init__.py
+++ b/conda_lock/_vendor/poetry/layouts/__init__.py
@@ -1,14 +1,13 @@
-from typing import Type
+from __future__ import annotations
 
-from .layout import Layout
-from .src import SrcLayout
-from .standard import StandardLayout
+from conda_lock._vendor.poetry.layouts.layout import Layout
+from conda_lock._vendor.poetry.layouts.src import SrcLayout
 
 
-_LAYOUTS = {"src": SrcLayout, "standard": StandardLayout}
+_LAYOUTS = {"src": SrcLayout, "standard": Layout}
 
 
-def layout(name):  # type: (str) -> Type[Layout]
+def layout(name: str) -> type[Layout]:
     if name not in _LAYOUTS:
         raise ValueError("Invalid layout")
 
diff --git a/conda_lock/_vendor/poetry/layouts/layout.py b/conda_lock/_vendor/poetry/layouts/layout.py
index c8bc0afd5..4fbb3d035 100644
--- a/conda_lock/_vendor/poetry/layouts/layout.py
+++ b/conda_lock/_vendor/poetry/layouts/layout.py
@@ -1,22 +1,22 @@
+from __future__ import annotations
+
+from pathlib import Path
 from typing import TYPE_CHECKING
-from typing import Optional
+from typing import Any
 
-from tomlkit import dumps
+from packaging.utils import canonicalize_name
+from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML
+from conda_lock._vendor.poetry.core.utils.helpers import module_name
+from tomlkit import inline_table
 from tomlkit import loads
 from tomlkit import table
-
-from conda_lock._vendor.poetry.utils.helpers import module_name
+from tomlkit.toml_document import TOMLDocument
 
 
 if TYPE_CHECKING:
-    from conda_lock._vendor.poetry.core.pyproject.toml import PyProjectTOML
-
-TESTS_DEFAULT = u"""from {package_name} import __version__
+    from typing import Mapping
 
-
-def test_version():
-    assert __version__ == '{version}'
-"""
+    from tomlkit.items import InlineTable
 
 
 POETRY_DEFAULT = """\
@@ -25,47 +25,42 @@ def test_version():
 version = ""
 description = ""
 authors = []
-
-[tool.poetry.dependencies]
-
-[tool.poetry.dev-dependencies]
-"""
-
-POETRY_WITH_LICENSE = """\
-[tool.poetry]
-name = ""
-version = ""
-description = ""
-authors = []
 license = ""
+readme = ""
+packages = []
 
 [tool.poetry.dependencies]
 
-[tool.poetry.dev-dependencies]
+[tool.poetry.group.dev.dependencies]
 """
 
-BUILD_SYSTEM_MIN_VERSION = "1.0.0"
-BUILD_SYSTEM_MAX_VERSION = None
+BUILD_SYSTEM_MIN_VERSION: str | None = None
+BUILD_SYSTEM_MAX_VERSION: str | None = None
 
 
-class Layout(object):
+class Layout:
     def __init__(
         self,
-        project,
-        version="0.1.0",
-        description="",
-        readme_format="md",
-        author=None,
-        license=None,
-        python="*",
-        dependencies=None,
-        dev_dependencies=None,
-    ):
-        self._project = project
-        self._package_name = module_name(project)
+        project: str,
+        version: str = "0.1.0",
+        description: str = "",
+        readme_format: str = "md",
+        author: str | None = None,
+        license: str | None = None,
+        python: str = "*",
+        dependencies: dict[str, str | Mapping[str, Any]] | None = None,
+        dev_dependencies: dict[str, str | Mapping[str, Any]] | None = None,
+    ) -> None:
+        self._project = canonicalize_name(project)
+        self._package_path_relative = Path(
+            *(module_name(part) for part in project.split("."))
+        )
+        self._package_name = ".".join(self._package_path_relative.parts)
         self._version = version
         self._description = description
-        self._readme_format = readme_format
+
+        self._readme_format = readme_format.lower()
+
         self._license = license
         self._python = python
         self._dependencies = dependencies or {}
@@ -76,7 +71,41 @@ def __init__(
 
         self._author = author
 
-    def create(self, path, with_tests=True):
+    @property
+    def basedir(self) -> Path:
+        return Path()
+
+    @property
+    def package_path(self) -> Path:
+        return self.basedir / self._package_path_relative
+
+    def get_package_include(self) -> InlineTable | None:
+        package = inline_table()
+
+        # If a project is created in the root directory (this is reasonable inside a
+        # docker container, eg )
+        # then parts will be empty.
+        parts = self._package_path_relative.parts
+        if not parts:
+            return None
+
+        include = parts[0]
+        package.append("include", include)  # type: ignore[no-untyped-call]
+
+        if self.basedir != Path():
+            package.append(  # type: ignore[no-untyped-call]
+                "from",
+                self.basedir.as_posix(),
+            )
+        else:
+            if include == self._project:
+                # package include and package name are the same,
+                # packages table is redundant here.
+                return None
+
+        return package
+
+    def create(self, path: Path, with_tests: bool = True) -> None:
         path.mkdir(parents=True, exist_ok=True)
 
         self._create_default(path)
@@ -87,78 +116,84 @@ def create(self, path, with_tests=True):
 
         self._write_poetry(path)
 
-    def generate_poetry_content(
-        self, original=None
-    ):  # type: (Optional["PyProjectTOML"]) -> str
+    def generate_poetry_content(self) -> TOMLDocument:
         template = POETRY_DEFAULT
-        if self._license:
-            template = POETRY_WITH_LICENSE
 
-        content = loads(template)
+        content: dict[str, Any] = loads(template)
+
         poetry_content = content["tool"]["poetry"]
         poetry_content["name"] = self._project
         poetry_content["version"] = self._version
         poetry_content["description"] = self._description
         poetry_content["authors"].append(self._author)
+
         if self._license:
             poetry_content["license"] = self._license
+        else:
+            poetry_content.remove("license")
+
+        poetry_content["readme"] = f"README.{self._readme_format}"
+        packages = self.get_package_include()
+        if packages:
+            poetry_content["packages"].append(packages)
+        else:
+            poetry_content.remove("packages")
 
         poetry_content["dependencies"]["python"] = self._python
 
         for dep_name, dep_constraint in self._dependencies.items():
             poetry_content["dependencies"][dep_name] = dep_constraint
 
-        for dep_name, dep_constraint in self._dev_dependencies.items():
-            poetry_content["dev-dependencies"][dep_name] = dep_constraint
+        if self._dev_dependencies:
+            for dep_name, dep_constraint in self._dev_dependencies.items():
+                poetry_content["group"]["dev"]["dependencies"][
+                    dep_name
+                ] = dep_constraint
+        else:
+            del poetry_content["group"]
 
         # Add build system
         build_system = table()
-        build_system_version = ">=" + BUILD_SYSTEM_MIN_VERSION
+        build_system_version = ""
+
+        if BUILD_SYSTEM_MIN_VERSION is not None:
+            build_system_version = ">=" + BUILD_SYSTEM_MIN_VERSION
         if BUILD_SYSTEM_MAX_VERSION is not None:
-            build_system_version += ",<" + BUILD_SYSTEM_MAX_VERSION
+            if build_system_version:
+                build_system_version += ","
+            build_system_version += "<" + BUILD_SYSTEM_MAX_VERSION
 
         build_system.add("requires", ["poetry-core" + build_system_version])
         build_system.add("build-backend", "poetry.core.masonry.api")
 
+        assert isinstance(content, TOMLDocument)
         content.add("build-system", build_system)
 
-        content = dumps(content)
-
-        if original and original.file.exists():
-            content = dumps(original.data) + "\n" + content
-
         return content
 
-    def _create_default(self, path, src=True):
-        raise NotImplementedError()
+    def _create_default(self, path: Path, src: bool = True) -> None:
+        package_path = path / self.package_path
+        package_path.mkdir(parents=True)
 
-    def _create_readme(self, path):
-        if self._readme_format == "rst":
-            readme_file = path / "README.rst"
-        else:
-            readme_file = path / "README.md"
+        package_init = package_path / "__init__.py"
+        package_init.touch()
 
+    def _create_readme(self, path: Path) -> Path:
+        readme_file = path.joinpath(f"README.{self._readme_format}")
         readme_file.touch()
+        return readme_file
 
-    def _create_tests(self, path):
+    @staticmethod
+    def _create_tests(path: Path) -> None:
         tests = path / "tests"
-        tests_init = tests / "__init__.py"
-        tests_default = tests / "test_{}.py".format(self._package_name)
-
         tests.mkdir()
-        tests_init.touch(exist_ok=False)
 
-        with tests_default.open("w", encoding="utf-8") as f:
-            f.write(
-                TESTS_DEFAULT.format(
-                    package_name=self._package_name, version=self._version
-                )
-            )
+        tests_init = tests / "__init__.py"
+        tests_init.touch(exist_ok=False)
 
-    def _write_poetry(self, path):
+    def _write_poetry(self, path: Path) -> None:
+        pyproject = PyProjectTOML(path / "pyproject.toml")
         content = self.generate_poetry_content()
-
-        poetry = path / "pyproject.toml"
-
-        with poetry.open("w", encoding="utf-8") as f:
-            f.write(content)
+        for section in content:
+            pyproject.data.append(section, content[section])
+        pyproject.save()
diff --git a/conda_lock/_vendor/poetry/layouts/src.py b/conda_lock/_vendor/poetry/layouts/src.py
index 06db7a71f..108d97316 100644
--- a/conda_lock/_vendor/poetry/layouts/src.py
+++ b/conda_lock/_vendor/poetry/layouts/src.py
@@ -1,19 +1,11 @@
-# -*- coding: utf-8 -*-
+from __future__ import annotations
 
-from .layout import Layout
+from pathlib import Path
 
-
-DEFAULT = u"""__version__ = '{version}'
-"""
+from conda_lock._vendor.poetry.layouts.layout import Layout
 
 
 class SrcLayout(Layout):
-    def _create_default(self, path):
-        package_path = path / "src" / self._package_name
-
-        package_init = package_path / "__init__.py"
-
-        package_path.mkdir(parents=True)
-
-        with package_init.open("w", encoding="utf-8") as f:
-            f.write(DEFAULT.format(version=self._version))
+    @property
+    def basedir(self) -> Path:
+        return Path("src")
diff --git a/conda_lock/_vendor/poetry/layouts/standard.py b/conda_lock/_vendor/poetry/layouts/standard.py
index eca4c435c..e69de29bb 100644
--- a/conda_lock/_vendor/poetry/layouts/standard.py
+++ b/conda_lock/_vendor/poetry/layouts/standard.py
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from .layout import Layout
-
-
-DEFAULT = u"""__version__ = '{version}'
-"""
-
-
-class StandardLayout(Layout):
-    def _create_default(self, path):
-        package_path = path / self._package_name
-
-        package_init = package_path / "__init__.py"
-
-        package_path.mkdir()
-
-        with package_init.open("w", encoding="utf-8") as f:
-            f.write(DEFAULT.format(version=self._version))
diff --git a/conda_lock/_vendor/poetry/locations.py b/conda_lock/_vendor/poetry/locations.py
index 001e1a9ef..0e3b884eb 100644
--- a/conda_lock/_vendor/poetry/locations.py
+++ b/conda_lock/_vendor/poetry/locations.py
@@ -1,19 +1,50 @@
+from __future__ import annotations
+
+import logging
 import os
+import sys
+
+from pathlib import Path
+
+from platformdirs import user_cache_path
+from platformdirs import user_config_path
+from platformdirs import user_data_path
+
+
+logger = logging.getLogger(__name__)
 
-from .utils._compat import Path
-from .utils.appdirs import user_cache_dir
-from .utils.appdirs import user_config_dir
-from .utils.appdirs import user_data_dir
+_APP_NAME = "pypoetry"
 
+DEFAULT_CACHE_DIR = user_cache_path(_APP_NAME, appauthor=False)
+CONFIG_DIR = Path(
+    os.getenv("POETRY_CONFIG_DIR")
+    or user_config_path(_APP_NAME, appauthor=False, roaming=True)
+)
 
-CACHE_DIR = user_cache_dir("pypoetry")
-CONFIG_DIR = user_config_dir("pypoetry")
+# platformdirs 2.0.0 corrected the OSX/macOS config directory from
+# /Users//Library/Application Support/ to
+# /Users//Library/Preferences/.
+#
+# For now we only deprecate use of the old directory.
+if sys.platform == "darwin":
+    _LEGACY_CONFIG_DIR = CONFIG_DIR.parent.parent / "Application Support" / _APP_NAME
+    config_toml = _LEGACY_CONFIG_DIR / "config.toml"
+    auth_toml = _LEGACY_CONFIG_DIR / "auth.toml"
 
-REPOSITORY_CACHE_DIR = Path(CACHE_DIR) / "cache" / "repositories"
+    if any(file.exists() for file in (auth_toml, config_toml)):
+        logger.warning(
+            "Configuration file exists at %s, reusing this directory.\n\nConsider"
+            " moving configuration to %s, as support for the legacy directory will be"
+            " removed in an upcoming release.",
+            _LEGACY_CONFIG_DIR,
+            CONFIG_DIR,
+        )
+        CONFIG_DIR = _LEGACY_CONFIG_DIR
 
 
-def data_dir():  # type: () -> Path
-    if os.getenv("POETRY_HOME"):
-        return Path(os.getenv("POETRY_HOME")).expanduser()
+def data_dir() -> Path:
+    poetry_home = os.getenv("POETRY_HOME")
+    if poetry_home:
+        return Path(poetry_home).expanduser()
 
-    return Path(user_data_dir("pypoetry", roaming=True))
+    return user_data_path(_APP_NAME, appauthor=False, roaming=True)
diff --git a/conda_lock/_vendor/poetry/masonry/api.py b/conda_lock/_vendor/poetry/masonry/api.py
index c6b6e3a39..f5dc6090c 100644
--- a/conda_lock/_vendor/poetry/masonry/api.py
+++ b/conda_lock/_vendor/poetry/masonry/api.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from conda_lock._vendor.poetry.core.masonry.api import build_sdist
 from conda_lock._vendor.poetry.core.masonry.api import build_wheel
 from conda_lock._vendor.poetry.core.masonry.api import get_requires_for_build_sdist
diff --git a/conda_lock/_vendor/poetry/masonry/builders/__init__.py b/conda_lock/_vendor/poetry/masonry/builders/__init__.py
index f1f02b72d..3fa6c921a 100644
--- a/conda_lock/_vendor/poetry/masonry/builders/__init__.py
+++ b/conda_lock/_vendor/poetry/masonry/builders/__init__.py
@@ -1 +1,6 @@
-from .editable import EditableBuilder
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.masonry.builders.editable import EditableBuilder
+
+
+__all__ = ["EditableBuilder"]
diff --git a/conda_lock/_vendor/poetry/masonry/builders/editable.py b/conda_lock/_vendor/poetry/masonry/builders/editable.py
index ec925e03b..4f443d136 100644
--- a/conda_lock/_vendor/poetry/masonry/builders/editable.py
+++ b/conda_lock/_vendor/poetry/masonry/builders/editable.py
@@ -1,27 +1,40 @@
-from __future__ import unicode_literals
+from __future__ import annotations
 
+import csv
 import hashlib
+import json
 import os
 import shutil
 
 from base64 import urlsafe_b64encode
+from pathlib import Path
+from typing import TYPE_CHECKING
 
+from conda_lock._vendor.poetry.core.constraints.version import Version
 from conda_lock._vendor.poetry.core.masonry.builders.builder import Builder
 from conda_lock._vendor.poetry.core.masonry.builders.sdist import SdistBuilder
 from conda_lock._vendor.poetry.core.masonry.utils.package_include import PackageInclude
-from conda_lock._vendor.poetry.core.semver.version import Version
+
 from conda_lock._vendor.poetry.utils._compat import WINDOWS
-from conda_lock._vendor.poetry.utils._compat import Path
 from conda_lock._vendor.poetry.utils._compat import decode
+from conda_lock._vendor.poetry.utils.env import build_environment
 from conda_lock._vendor.poetry.utils.helpers import is_dir_writable
+from conda_lock._vendor.poetry.utils.pip import pip_install
+
 
+if TYPE_CHECKING:
+    from conda_lock._vendor.cleo.io.io import IO
+
+    from conda_lock._vendor.poetry.poetry import Poetry
+    from conda_lock._vendor.poetry.utils.env import Env
 
 SCRIPT_TEMPLATE = """\
 #!{python}
+import sys
 from {module} import {callable_holder}
 
 if __name__ == '__main__':
-    {callable_}()
+    sys.exit({callable_}())
 """
 
 WINDOWS_CMD_TEMPLATE = """\
@@ -30,17 +43,16 @@
 
 
 class EditableBuilder(Builder):
-    def __init__(self, poetry, env, io):
-        super(EditableBuilder, self).__init__(poetry)
+    def __init__(self, poetry: Poetry, env: Env, io: IO) -> None:
+        super().__init__(poetry)
 
         self._env = env
         self._io = io
 
-    def build(self):
+    def build(self, target_dir: Path | None = None) -> Path:
         self._debug(
-            "  - Building package {} in editable mode".format(
-                self._package.name
-            )
+            f"  - Building package {self._package.name} in"
+            " editable mode"
         )
 
         if self._package.build_script:
@@ -48,27 +60,40 @@ def build(self):
                 self._debug(
                     "  - Falling back on using a setup.py"
                 )
-
-                return self._setup_build()
+                self._setup_build()
+                path: Path = self._path
+                return path
 
             self._run_build_script(self._package.build_script)
 
+        for removed in self._env.site_packages.remove_distribution_files(
+            distribution_name=self._package.name
+        ):
+            self._debug(
+                f"  - Removed {removed.name} directory from"
+                f" {removed.parent}"
+            )
+
         added_files = []
         added_files += self._add_pth()
         added_files += self._add_scripts()
         self._add_dist_info(added_files)
 
-    def _run_build_script(self, build_script):
-        self._debug("  - Executing build script: {}".format(build_script))
-        self._env.run("python", str(self._path.joinpath(build_script)), call=True)
+        path = self._path
+        return path
+
+    def _run_build_script(self, build_script: str) -> None:
+        with build_environment(poetry=self._poetry, env=self._env, io=self._io) as env:
+            self._debug(f"  - Executing build script: {build_script}")
+            env.run("python", str(self._path.joinpath(build_script)), call=True)
 
-    def _setup_build(self):
+    def _setup_build(self) -> None:
         builder = SdistBuilder(self._poetry)
         setup = self._path / "setup.py"
         has_setup = setup.exists()
 
         if has_setup:
-            self._io.write_line(
+            self._io.write_error_line(
                 "A setup.py file already exists. Using it."
             )
         else:
@@ -76,15 +101,15 @@ def _setup_build(self):
                 f.write(decode(builder.build_setup()))
 
         try:
-            if self._env.pip_version < Version(19, 0):
-                self._env.run_pip("install", "-e", str(self._path), "--no-deps")
+            if self._env.pip_version < Version.from_parts(19, 0):
+                pip_install(self._path, self._env, upgrade=True, editable=True)
             else:
                 # Temporarily rename pyproject.toml
                 shutil.move(
                     str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
                 )
                 try:
-                    self._env.run_pip("install", "-e", str(self._path), "--no-deps")
+                    pip_install(self._path, self._env, upgrade=True, editable=True)
                 finally:
                     shutil.move(
                         str(self._poetry.file.with_suffix(".tmp")),
@@ -94,39 +119,45 @@ def _setup_build(self):
             if not has_setup:
                 os.remove(str(setup))
 
-    def _add_pth(self):
-        paths = set()
-        for include in self._module.includes:
-            if isinstance(include, PackageInclude) and (
-                include.is_module() or include.is_package()
-            ):
-                paths.add(include.base.resolve().as_posix())
-
-        content = ""
-        for path in paths:
-            content += decode(path + os.linesep)
+    def _add_pth(self) -> list[Path]:
+        paths = {
+            include.base.resolve().as_posix()
+            for include in self._module.includes
+            if isinstance(include, PackageInclude)
+            and (include.is_module() or include.is_package())
+        }
 
+        content = "".join(decode(path + os.linesep) for path in paths)
         pth_file = Path(self._module.name).with_suffix(".pth")
+
+        # remove any pre-existing pth files for this package
+        for file in self._env.site_packages.find(path=pth_file, writable_only=True):
+            self._debug(
+                f"  - Removing existing {file.name} from {file.parent}"
+                f" for {self._poetry.file.parent}"
+            )
+            # We can't use unlink(missing_ok=True) because it's not always available
+            if file.exists():
+                file.unlink()
+
         try:
             pth_file = self._env.site_packages.write_text(
                 pth_file, content, encoding="utf-8"
             )
             self._debug(
-                "  - Adding {} to {} for {}".format(
-                    pth_file.name, pth_file.parent, self._poetry.file.parent
-                )
+                f"  - Adding {pth_file.name} to {pth_file.parent} for"
+                f" {self._poetry.file.parent}"
             )
             return [pth_file]
         except OSError:
             # TODO: Replace with PermissionError
-            self._io.error_line(
-                "  - Failed to create {} for {}".format(
-                    pth_file.name, self._poetry.file.parent
-                )
+            self._io.write_error_line(
+                f"  - Failed to create {pth_file.name} for"
+                f" {self._poetry.file.parent}"
             )
             return []
 
-    def _add_scripts(self):
+    def _add_scripts(self) -> list[Path]:
         added = []
         entry_points = self.convert_entry_points()
 
@@ -134,10 +165,9 @@ def _add_scripts(self):
             if is_dir_writable(path=scripts_path, create=True):
                 break
         else:
-            self._io.error_line(
-                "  - Failed to find a suitable script installation directory for {}".format(
-                    self._poetry.file.parent
-                )
+            self._io.write_error_line(
+                "  - Failed to find a suitable script installation directory for"
+                f" {self._poetry.file.parent}"
             )
             return []
 
@@ -149,9 +179,7 @@ def _add_scripts(self):
 
             script_file = scripts_path.joinpath(name)
             self._debug(
-                "  - Adding the {} script to {}".format(
-                    name, scripts_path
-                )
+                f"  - Adding the {name} script to {scripts_path}"
             )
             with script_file.open("w", encoding="utf-8") as f:
                 f.write(
@@ -173,9 +201,8 @@ def _add_scripts(self):
                 cmd_script = script_file.with_suffix(".cmd")
                 cmd = WINDOWS_CMD_TEMPLATE.format(python=self._env.python, script=name)
                 self._debug(
-                    "  - Adding the {} script wrapper to {}".format(
-                        cmd_script.name, scripts_path
-                    )
+                    f"  - Adding the {cmd_script.name} script wrapper to"
+                    f" {scripts_path}"
                 )
 
                 with cmd_script.open("w", encoding="utf-8") as f:
@@ -185,31 +212,17 @@ def _add_scripts(self):
 
         return added
 
-    def _add_dist_info(self, added_files):
+    def _add_dist_info(self, added_files: list[Path]) -> None:
         from conda_lock._vendor.poetry.core.masonry.builders.wheel import WheelBuilder
 
         added_files = added_files[:]
 
         builder = WheelBuilder(self._poetry)
-
-        dist_info_path = Path(builder.dist_info)
-        for dist_info in self._env.site_packages.find(
-            dist_info_path, writable_only=True
-        ):
-            if dist_info.exists():
-                self._debug(
-                    "  - Removing existing {} directory from {}".format(
-                        dist_info.name, dist_info.parent
-                    )
-                )
-                shutil.rmtree(str(dist_info))
-
-        dist_info = self._env.site_packages.mkdir(dist_info_path)
+        dist_info = self._env.site_packages.mkdir(Path(builder.dist_info))
 
         self._debug(
-            "  - Adding the {} directory to {}".format(
-                dist_info.name, dist_info.parent
-            )
+            f"  - Adding the {dist_info.name} directory to"
+            f" {dist_info.parent}"
         )
 
         with dist_info.joinpath("METADATA").open("w", encoding="utf-8") as f:
@@ -230,16 +243,30 @@ def _add_dist_info(self, added_files):
 
             added_files.append(dist_info.joinpath("entry_points.txt"))
 
-        with dist_info.joinpath("RECORD").open("w", encoding="utf-8") as f:
+        # write PEP 610 metadata
+        direct_url_json = dist_info.joinpath("direct_url.json")
+        direct_url_json.write_text(
+            json.dumps(
+                {
+                    "dir_info": {"editable": True},
+                    "url": self._poetry.file.path.parent.absolute().as_uri(),
+                }
+            )
+        )
+        added_files.append(direct_url_json)
+
+        record = dist_info.joinpath("RECORD")
+        with record.open("w", encoding="utf-8", newline="") as f:
+            csv_writer = csv.writer(f)
             for path in added_files:
                 hash = self._get_file_hash(path)
                 size = path.stat().st_size
-                f.write("{},sha256={},{}\n".format(str(path), hash, size))
+                csv_writer.writerow((path, f"sha256={hash}", size))
 
             # RECORD itself is recorded with no hash or size
-            f.write("{},,\n".format(dist_info.joinpath("RECORD")))
+            csv_writer.writerow((record, "", ""))
 
-    def _get_file_hash(self, filepath):
+    def _get_file_hash(self, filepath: Path) -> str:
         hashsum = hashlib.sha256()
         with filepath.open("rb") as src:
             while True:
@@ -252,6 +279,6 @@ def _get_file_hash(self, filepath):
 
         return urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
 
-    def _debug(self, msg):
+    def _debug(self, msg: str) -> None:
         if self._io.is_debug():
             self._io.write_line(msg)
diff --git a/conda_lock/_vendor/poetry/mixology/__init__.py b/conda_lock/_vendor/poetry/mixology/__init__.py
index 50fbffb27..3e29eb24c 100644
--- a/conda_lock/_vendor/poetry/mixology/__init__.py
+++ b/conda_lock/_vendor/poetry/mixology/__init__.py
@@ -1,7 +1,18 @@
-from .version_solver import VersionSolver
+from __future__ import annotations
 
+from typing import TYPE_CHECKING
 
-def resolve_version(root, provider, locked=None, use_latest=None):
-    solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest)
+from conda_lock._vendor.poetry.mixology.version_solver import VersionSolver
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
+
+    from conda_lock._vendor.poetry.mixology.result import SolverResult
+    from conda_lock._vendor.poetry.puzzle.provider import Provider
+
+
+def resolve_version(root: ProjectPackage, provider: Provider) -> SolverResult:
+    solver = VersionSolver(root, provider)
 
     return solver.solve()
diff --git a/conda_lock/_vendor/poetry/mixology/assignment.py b/conda_lock/_vendor/poetry/mixology/assignment.py
index e288c5da5..80214b9ba 100644
--- a/conda_lock/_vendor/poetry/mixology/assignment.py
+++ b/conda_lock/_vendor/poetry/mixology/assignment.py
@@ -1,7 +1,15 @@
-from typing import Any
+from __future__ import annotations
 
-from .incompatibility import Incompatibility
-from .term import Term
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.mixology.term import Term
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.mixology.incompatibility import Incompatibility
 
 
 class Assignment(Term):
@@ -9,36 +17,46 @@ class Assignment(Term):
     A term in a PartialSolution that tracks some additional metadata.
     """
 
-    def __init__(self, dependency, is_positive, decision_level, index, cause=None):
-        super(Assignment, self).__init__(dependency, is_positive)
+    def __init__(
+        self,
+        dependency: Dependency,
+        is_positive: bool,
+        decision_level: int,
+        index: int,
+        cause: Incompatibility | None = None,
+    ) -> None:
+        super().__init__(dependency, is_positive)
 
         self._decision_level = decision_level
         self._index = index
         self._cause = cause
 
     @property
-    def decision_level(self):  # type: () -> int
+    def decision_level(self) -> int:
         return self._decision_level
 
     @property
-    def index(self):  # type: () -> int
+    def index(self) -> int:
         return self._index
 
     @property
-    def cause(self):  # type: () -> Incompatibility
+    def cause(self) -> Incompatibility | None:
         return self._cause
 
     @classmethod
-    def decision(
-        cls, package, decision_level, index
-    ):  # type: (Any, int, int) -> Assignment
+    def decision(cls, package: Package, decision_level: int, index: int) -> Assignment:
         return cls(package.to_dependency(), True, decision_level, index)
 
     @classmethod
     def derivation(
-        cls, dependency, is_positive, cause, decision_level, index
-    ):  # type: (Any, bool, Incompatibility, int, int) -> Assignment
+        cls,
+        dependency: Dependency,
+        is_positive: bool,
+        cause: Incompatibility,
+        decision_level: int,
+        index: int,
+    ) -> Assignment:
         return cls(dependency, is_positive, decision_level, index, cause)
 
-    def is_decision(self):  # type: () -> bool
+    def is_decision(self) -> bool:
         return self._cause is None
diff --git a/conda_lock/_vendor/poetry/mixology/failure.py b/conda_lock/_vendor/poetry/mixology/failure.py
index 2f53b05b2..e6c529614 100644
--- a/conda_lock/_vendor/poetry/mixology/failure.py
+++ b/conda_lock/_vendor/poetry/mixology/failure.py
@@ -1,36 +1,39 @@
-from typing import Dict
-from typing import List
-from typing import Tuple
+from __future__ import annotations
 
-from conda_lock._vendor.poetry.core.semver import parse_constraint
+from typing import TYPE_CHECKING
 
-from .incompatibility import Incompatibility
-from .incompatibility_cause import ConflictCause
-from .incompatibility_cause import PythonCause
+from conda_lock._vendor.poetry.core.constraints.version import parse_constraint
+
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import ConflictCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import PythonCause
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.mixology.incompatibility import Incompatibility
 
 
 class SolveFailure(Exception):
-    def __init__(self, incompatibility):  # type: (Incompatibility) -> None
+    def __init__(self, incompatibility: Incompatibility) -> None:
         self._incompatibility = incompatibility
 
     @property
-    def message(self):
+    def message(self) -> str:
         return str(self)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return _Writer(self._incompatibility).write()
 
 
 class _Writer:
-    def __init__(self, root):  # type: (Incompatibility) -> None
+    def __init__(self, root: Incompatibility) -> None:
         self._root = root
-        self._derivations = {}  # type: Dict[Incompatibility, int]
-        self._lines = []  # type: List[Tuple[str, int]]
-        self._line_numbers = {}  # type: Dict[Incompatibility, int]
+        self._derivations: dict[Incompatibility, int] = {}
+        self._lines: list[tuple[str, int | None]] = []
+        self._line_numbers: dict[Incompatibility, int] = {}
 
         self._count_derivations(self._root)
 
-    def write(self):
+    def write(self) -> str:
         buffer = []
 
         required_python_version_notification = False
@@ -38,11 +41,10 @@ def write(self):
             if isinstance(incompatibility.cause, PythonCause):
                 if not required_python_version_notification:
                     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
-                        )
+                        "The current project's Python requirement"
+                        f" ({incompatibility.cause.root_python_version}) is not"
+                        " compatible with some of the required packages Python"
+                        " requirement:"
                     )
                     required_python_version_notification = True
 
@@ -51,27 +53,23 @@ def write(self):
                 )
                 constraint = parse_constraint(incompatibility.cause.python_version)
                 buffer.append(
-                    "  - {} requires Python {}, so it will not be satisfied for Python {}".format(
-                        incompatibility.terms[0].dependency.name,
-                        incompatibility.cause.python_version,
-                        root_constraint.difference(constraint),
-                    )
+                    f"  - {incompatibility.terms[0].dependency.name} requires Python"
+                    f" {incompatibility.cause.python_version}, so it will not be"
+                    f" satisfied for Python {root_constraint.difference(constraint)}"
                 )
 
         if required_python_version_notification:
             buffer.append("")
 
         if isinstance(self._root.cause, ConflictCause):
-            self._visit(self._root, {})
+            self._visit(self._root)
         else:
-            self._write(
-                self._root, "Because {}, version solving failed.".format(self._root)
-            )
+            self._write(self._root, f"Because {self._root}, version solving failed.")
 
         padding = (
             0
             if not self._line_numbers
-            else len("({}) ".format(list(self._line_numbers.values())[-1]))
+            else len(f"({list(self._line_numbers.values())[-1]}) ")
         )
 
         last_was_empty = False
@@ -88,7 +86,7 @@ def write(self):
 
             number = line[-1]
             if number is not None:
-                message = "({})".format(number).ljust(padding) + message
+                message = f"({number})".ljust(padding) + message
             else:
                 message = " " * padding + message
 
@@ -97,8 +95,8 @@ def write(self):
         return "\n".join(buffer)
 
     def _write(
-        self, incompatibility, message, numbered=False
-    ):  # type: (Incompatibility, str, bool) -> None
+        self, incompatibility: Incompatibility, message: str, numbered: bool = False
+    ) -> None:
         if numbered:
             number = len(self._line_numbers) + 1
             self._line_numbers[incompatibility] = number
@@ -107,14 +105,17 @@ def _write(
             self._lines.append((message, None))
 
     def _visit(
-        self, incompatibility, details_for_incompatibility, conclusion=False
-    ):  # type: (Incompatibility, Dict, bool) -> None
+        self,
+        incompatibility: Incompatibility,
+        conclusion: bool = False,
+    ) -> None:
         numbered = conclusion or self._derivations[incompatibility] > 1
         conjunction = "So," if conclusion or incompatibility == self._root else "And"
         incompatibility_string = str(incompatibility)
 
-        cause = incompatibility.cause  # type: ConflictCause
-        details_for_cause = {}
+        cause = incompatibility.cause
+        assert isinstance(cause, ConflictCause)
+
         if isinstance(cause.conflict.cause, ConflictCause) and isinstance(
             cause.other.cause, ConflictCause
         ):
@@ -122,14 +123,12 @@ def _visit(
             other_line = self._line_numbers.get(cause.other)
 
             if conflict_line is not None and other_line is not None:
+                reason = cause.conflict.and_to_string(
+                    cause.other, conflict_line, other_line
+                )
                 self._write(
                     incompatibility,
-                    "Because {}, {}.".format(
-                        cause.conflict.and_to_string(
-                            cause.other, details_for_cause, conflict_line, other_line
-                        ),
-                        incompatibility_string,
-                    ),
+                    f"Because {reason}, {incompatibility_string}.",
                     numbered=numbered,
                 )
             elif conflict_line is not None or other_line is not None:
@@ -137,17 +136,16 @@ def _visit(
                     with_line = cause.conflict
                     without_line = cause.other
                     line = conflict_line
-                else:
+                elif other_line is not None:
                     with_line = cause.other
                     without_line = cause.conflict
                     line = other_line
 
-                self._visit(without_line, details_for_cause)
+                self._visit(without_line)
                 self._write(
                     incompatibility,
-                    "{} because {} ({}), {}.".format(
-                        conjunction, str(with_line), line, incompatibility_string
-                    ),
+                    f"{conjunction} because {with_line!s} ({line}),"
+                    f" {incompatibility_string}.",
                     numbered=numbered,
                 )
             else:
@@ -157,27 +155,24 @@ def _visit(
                 if single_line_other or single_line_conflict:
                     first = cause.conflict if single_line_other else cause.other
                     second = cause.other if single_line_other else cause.conflict
-                    self._visit(first, details_for_cause)
-                    self._visit(second, details_for_cause)
+                    self._visit(first)
+                    self._visit(second)
                     self._write(
                         incompatibility,
-                        "Thus, {}.".format(incompatibility_string),
+                        f"Thus, {incompatibility_string}.",
                         numbered=numbered,
                     )
                 else:
-                    self._visit(cause.conflict, {}, conclusion=True)
+                    self._visit(cause.conflict, conclusion=True)
                     self._lines.append(("", None))
 
-                    self._visit(cause.other, details_for_cause)
+                    self._visit(cause.other)
 
                     self._write(
                         incompatibility,
-                        "{} because {} ({}), {}".format(
-                            conjunction,
-                            str(cause.conflict),
-                            self._line_numbers[cause.conflict],
-                            incompatibility_string,
-                        ),
+                        f"{conjunction} because"
+                        f" {cause.conflict!s} ({self._line_numbers[cause.conflict]}),"
+                        f" {incompatibility_string}",
                         numbered=numbered,
                     )
         elif isinstance(cause.conflict.cause, ConflictCause) or isinstance(
@@ -196,66 +191,51 @@ def _visit(
 
             derived_line = self._line_numbers.get(derived)
             if derived_line is not None:
+                reason = ext.and_to_string(derived, None, derived_line)
                 self._write(
                     incompatibility,
-                    "Because {}, {}.".format(
-                        ext.and_to_string(
-                            derived, details_for_cause, None, derived_line
-                        ),
-                        incompatibility_string,
-                    ),
+                    f"Because {reason}, {incompatibility_string}.",
                     numbered=numbered,
                 )
             elif self._is_collapsible(derived):
-                derived_cause = derived.cause  # type: ConflictCause
+                derived_cause = derived.cause
+                assert isinstance(derived_cause, ConflictCause)
                 if isinstance(derived_cause.conflict.cause, ConflictCause):
                     collapsed_derived = derived_cause.conflict
+                    collapsed_ext = derived_cause.other
                 else:
                     collapsed_derived = derived_cause.other
 
-                if isinstance(derived_cause.conflict.cause, ConflictCause):
-                    collapsed_ext = derived_cause.other
-                else:
                     collapsed_ext = derived_cause.conflict
 
-                details_for_cause = {}
-
-                self._visit(collapsed_derived, details_for_cause)
+                self._visit(collapsed_derived)
+                reason = collapsed_ext.and_to_string(ext, None, None)
                 self._write(
                     incompatibility,
-                    "{} because {}, {}.".format(
-                        conjunction,
-                        collapsed_ext.and_to_string(ext, details_for_cause, None, None),
-                        incompatibility_string,
-                    ),
+                    f"{conjunction} because {reason}, {incompatibility_string}.",
                     numbered=numbered,
                 )
             else:
-                self._visit(derived, details_for_cause)
+                self._visit(derived)
                 self._write(
                     incompatibility,
-                    "{} because {}, {}.".format(
-                        conjunction, str(ext), incompatibility_string
-                    ),
+                    f"{conjunction} because {ext!s}, {incompatibility_string}.",
                     numbered=numbered,
                 )
         else:
+            reason = cause.conflict.and_to_string(cause.other, None, None)
             self._write(
                 incompatibility,
-                "Because {}, {}.".format(
-                    cause.conflict.and_to_string(
-                        cause.other, details_for_cause, None, None
-                    ),
-                    incompatibility_string,
-                ),
+                f"Because {reason}, {incompatibility_string}.",
                 numbered=numbered,
             )
 
-    def _is_collapsible(self, incompatibility):  # type: (Incompatibility) -> bool
+    def _is_collapsible(self, incompatibility: Incompatibility) -> bool:
         if self._derivations[incompatibility] > 1:
             return False
 
-        cause = incompatibility.cause  # type: ConflictCause
+        cause = incompatibility.cause
+        assert isinstance(cause, ConflictCause)
         if isinstance(cause.conflict.cause, ConflictCause) and isinstance(
             cause.other.cause, ConflictCause
         ):
@@ -274,12 +254,12 @@ def _is_collapsible(self, incompatibility):  # type: (Incompatibility) -> bool
 
         return complex not in self._line_numbers
 
-    def _is_single_line(self, cause):  # type: (ConflictCause) -> bool
+    def _is_single_line(self, cause: ConflictCause) -> bool:
         return not isinstance(cause.conflict.cause, ConflictCause) and not isinstance(
             cause.other.cause, ConflictCause
         )
 
-    def _count_derivations(self, incompatibility):  # type: (Incompatibility) -> None
+    def _count_derivations(self, incompatibility: Incompatibility) -> None:
         if incompatibility in self._derivations:
             self._derivations[incompatibility] += 1
         else:
diff --git a/conda_lock/_vendor/poetry/mixology/incompatibility.py b/conda_lock/_vendor/poetry/mixology/incompatibility.py
index bba55bb20..b7b9fc332 100644
--- a/conda_lock/_vendor/poetry/mixology/incompatibility.py
+++ b/conda_lock/_vendor/poetry/mixology/incompatibility.py
@@ -1,29 +1,32 @@
-from typing import Dict
-from typing import Generator
-from typing import List
+from __future__ import annotations
 
-from .incompatibility_cause import ConflictCause
-from .incompatibility_cause import DependencyCause
-from .incompatibility_cause import IncompatibilityCause
-from .incompatibility_cause import NoVersionsCause
-from .incompatibility_cause import PackageNotFoundCause
-from .incompatibility_cause import PlatformCause
-from .incompatibility_cause import PythonCause
-from .incompatibility_cause import RootCause
-from .term import Term
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import ConflictCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import DependencyCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import NoVersionsCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import PlatformCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import PythonCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import RootCause
+
+
+if TYPE_CHECKING:
+    from collections.abc import Callable
+    from collections.abc import Iterator
+
+    from conda_lock._vendor.poetry.mixology.incompatibility_cause import IncompatibilityCause
+    from conda_lock._vendor.poetry.mixology.term import Term
 
 
 class Incompatibility:
-    def __init__(
-        self, terms, cause
-    ):  # type: (List[Term], IncompatibilityCause) -> None
+    def __init__(self, terms: list[Term], cause: IncompatibilityCause) -> None:
         # Remove the root package from generated incompatibilities, since it will
         # always be satisfied. This makes error reporting clearer, and may also
         # make solving more efficient.
         if (
             len(terms) != 1
             and isinstance(cause, ConflictCause)
-            and any([term.is_positive() and term.dependency.is_root for term in terms])
+            and any(term.is_positive() and term.dependency.is_root for term in terms)
         ):
             terms = [
                 term
@@ -31,17 +34,14 @@ def __init__(
                 if not term.is_positive() or not term.dependency.is_root
             ]
 
-        if (
-            len(terms) == 1
+        if len(terms) != 1 and (
             # Short-circuit in the common case of a two-term incompatibility with
             # two different packages (for example, a dependency).
-            or len(terms) == 2
-            and terms[0].dependency.complete_name != terms[-1].dependency.complete_name
+            len(terms) != 2
+            or terms[0].dependency.complete_name == terms[-1].dependency.complete_name
         ):
-            pass
-        else:
             # Coalesce multiple terms about the same package if possible.
-            by_name = {}  # type: Dict[str, Dict[str, Term]]
+            by_name: dict[str, dict[str, Term]] = {}
             for term in terms:
                 if term.dependency.complete_name not in by_name:
                     by_name[term.dependency.complete_name] = {}
@@ -50,14 +50,16 @@ def __init__(
                 ref = term.dependency.complete_name
 
                 if ref in by_ref:
-                    by_ref[ref] = by_ref[ref].intersect(term)
-
-                    # If we have two terms that refer to the same package but have a null
-                    # intersection, they're mutually exclusive, making this incompatibility
-                    # irrelevant, since we already know that mutually exclusive version
-                    # ranges are incompatible. We should never derive an irrelevant
-                    # incompatibility.
-                    assert by_ref[ref] is not None
+                    value = by_ref[ref].intersect(term)
+
+                    # If we have two terms that refer to the same package but have a
+                    # null intersection, they're mutually exclusive, making this
+                    # incompatibility irrelevant, since we already know that mutually
+                    # exclusive version ranges are incompatible. We should never derive
+                    # an irrelevant incompatibility.
+                    err_msg = f"Package '{ref}' is listed as a dependency of itself."
+                    assert value is not None, err_msg
+                    by_ref[ref] = value
                 else:
                     by_ref[ref] = term
 
@@ -78,35 +80,35 @@ def __init__(
         self._cause = cause
 
     @property
-    def terms(self):  # type: () -> List[Term]
+    def terms(self) -> list[Term]:
         return self._terms
 
     @property
-    def cause(self):  # type: () -> IncompatibilityCause
+    def cause(self) -> IncompatibilityCause:
         return self._cause
 
     @property
-    def external_incompatibilities(self):  # type: () -> Generator[Incompatibility]
+    def external_incompatibilities(
+        self,
+    ) -> Iterator[Incompatibility]:
         """
         Returns all external incompatibilities in this incompatibility's
         derivation graph.
         """
         if isinstance(self._cause, ConflictCause):
-            cause = self._cause  # type: ConflictCause
-            for incompatibility in cause.conflict.external_incompatibilities:
-                yield incompatibility
+            cause: ConflictCause = self._cause
+            yield from cause.conflict.external_incompatibilities
 
-            for incompatibility in cause.other.external_incompatibilities:
-                yield incompatibility
+            yield from cause.other.external_incompatibilities
         else:
             yield self
 
-    def is_failure(self):  # type: () -> bool
+    def is_failure(self) -> bool:
         return len(self._terms) == 0 or (
             len(self._terms) == 1 and self._terms[0].dependency.is_root
         )
 
-    def __str__(self):
+    def __str__(self) -> str:
         if isinstance(self._cause, DependencyCause):
             assert len(self._terms) == 2
 
@@ -115,85 +117,71 @@ def __str__(self):
             assert depender.is_positive()
             assert not dependee.is_positive()
 
-            return "{} depends on {}".format(
-                self._terse(depender, allow_every=True), self._terse(dependee)
+            return (
+                f"{self._terse(depender, allow_every=True)} depends on"
+                f" {self._terse(dependee)}"
             )
         elif isinstance(self._cause, PythonCause):
             assert len(self._terms) == 1
             assert self._terms[0].is_positive()
 
-            cause = self._cause  # type: PythonCause
-            text = "{} requires ".format(self._terse(self._terms[0], allow_every=True))
-            text += "Python {}".format(cause.python_version)
+            text = f"{self._terse(self._terms[0], allow_every=True)} requires "
+            text += f"Python {self._cause.python_version}"
 
             return text
         elif isinstance(self._cause, PlatformCause):
             assert len(self._terms) == 1
             assert self._terms[0].is_positive()
 
-            cause = self._cause  # type: PlatformCause
-            text = "{} requires ".format(self._terse(self._terms[0], allow_every=True))
-            text += "platform {}".format(cause.platform)
+            text = f"{self._terse(self._terms[0], allow_every=True)} requires "
+            text += f"platform {self._cause.platform}"
 
             return text
         elif isinstance(self._cause, NoVersionsCause):
             assert len(self._terms) == 1
             assert self._terms[0].is_positive()
 
-            return "no versions of {} match {}".format(
-                self._terms[0].dependency.name, self._terms[0].constraint
+            return (
+                f"no versions of {self._terms[0].dependency.name} match"
+                f" {self._terms[0].constraint}"
             )
-        elif isinstance(self._cause, PackageNotFoundCause):
-            assert len(self._terms) == 1
-            assert self._terms[0].is_positive()
-
-            return "{} doesn't exist".format(self._terms[0].dependency.name)
         elif isinstance(self._cause, RootCause):
             assert len(self._terms) == 1
             assert not self._terms[0].is_positive()
             assert self._terms[0].dependency.is_root
 
-            return "{} is {}".format(
-                self._terms[0].dependency.name, self._terms[0].dependency.constraint
+            return (
+                f"{self._terms[0].dependency.name} is"
+                f" {self._terms[0].dependency.constraint}"
             )
         elif self.is_failure():
             return "version solving failed"
 
         if len(self._terms) == 1:
             term = self._terms[0]
-            if term.constraint.is_any():
-                return "{} is {}".format(
-                    term.dependency.name,
-                    "forbidden" if term.is_positive() else "required",
-                )
-            else:
-                return "{} is {}".format(
-                    term.dependency.name,
-                    "forbidden" if term.is_positive() else "required",
-                )
+            verb = "forbidden" if term.is_positive() else "required"
+            return f"{term.dependency.name} is {verb}"
 
         if len(self._terms) == 2:
             term1 = self._terms[0]
             term2 = self._terms[1]
 
             if term1.is_positive() == term2.is_positive():
-                if term1.is_positive():
-                    package1 = (
-                        term1.dependency.name
-                        if term1.constraint.is_any()
-                        else self._terse(term1)
-                    )
-                    package2 = (
-                        term2.dependency.name
-                        if term2.constraint.is_any()
-                        else self._terse(term2)
-                    )
-
-                    return "{} is incompatible with {}".format(package1, package2)
-                else:
-                    return "either {} or {}".format(
-                        self._terse(term1), self._terse(term2)
-                    )
+                if not term1.is_positive():
+                    return f"either {self._terse(term1)} or {self._terse(term2)}"
+
+                package1 = (
+                    term1.dependency.name
+                    if term1.constraint.is_any()
+                    else self._terse(term1)
+                )
+                package2 = (
+                    term2.dependency.name
+                    if term2.constraint.is_any()
+                    else self._terse(term2)
+                )
+
+                return f"{package1} is incompatible with {package2}"
 
         positive = []
         negative = []
@@ -205,67 +193,67 @@ def __str__(self):
                 negative.append(self._terse(term))
 
         if positive and negative:
-            if len(positive) == 1:
-                positive_term = [term for term in self._terms if term.is_positive()][0]
+            if len(positive) != 1:
+                return f"if {' and '.join(positive)} then {' or '.join(negative)}"
 
-                return "{} requires {}".format(
-                    self._terse(positive_term, allow_every=True), " or ".join(negative)
-                )
-            else:
-                return "if {} then {}".format(
-                    " and ".join(positive), " or ".join(negative)
-                )
+            positive_term = [term for term in self._terms if term.is_positive()][0]
+            return (
+                f"{self._terse(positive_term, allow_every=True)} requires"
+                f" {' or '.join(negative)}"
+            )
         elif positive:
-            return "one of {} must be false".format(" or ".join(positive))
+            return f"one of {' or '.join(positive)} must be false"
         else:
-            return "one of {} must be true".format(" or ".join(negative))
+            return f"one of {' or '.join(negative)} must be true"
 
     def and_to_string(
-        self, other, details, this_line, other_line
-    ):  # type: (Incompatibility, dict, int, int) -> str
-        requires_both = self._try_requires_both(other, details, this_line, other_line)
+        self,
+        other: Incompatibility,
+        this_line: int | None,
+        other_line: int | None,
+    ) -> str:
+        requires_both = self._try_requires_both(other, this_line, other_line)
         if requires_both is not None:
             return requires_both
 
-        requires_through = self._try_requires_through(
-            other, details, this_line, other_line
-        )
+        requires_through = self._try_requires_through(other, this_line, other_line)
         if requires_through is not None:
             return requires_through
 
-        requires_forbidden = self._try_requires_forbidden(
-            other, details, this_line, other_line
-        )
+        requires_forbidden = self._try_requires_forbidden(other, this_line, other_line)
         if requires_forbidden is not None:
             return requires_forbidden
 
         buffer = [str(self)]
         if this_line is not None:
-            buffer.append(" " + str(this_line))
+            buffer.append(f" {this_line!s}")
 
-        buffer.append(" and {}".format(str(other)))
+        buffer.append(f" and {other!s}")
 
         if other_line is not None:
-            buffer.append(" " + str(other_line))
+            buffer.append(f" {other_line!s}")
 
         return "\n".join(buffer)
 
     def _try_requires_both(
-        self, other, details, this_line, other_line
-    ):  # type: (Incompatibility, dict, int, int) -> str
+        self,
+        other: Incompatibility,
+        this_line: int | None,
+        other_line: int | None,
+    ) -> str | None:
         if len(self._terms) == 1 or len(other.terms) == 1:
-            return
+            return None
 
         this_positive = self._single_term_where(lambda term: term.is_positive())
         if this_positive is None:
-            return
+            return None
 
         other_positive = other._single_term_where(lambda term: term.is_positive())
         if other_positive is None:
-            return
+            return None
 
         if this_positive.dependency != other_positive.dependency:
-            return
+            return None
 
         this_negatives = " or ".join(
             [self._terse(term) for term in self._terms if not term.is_positive()]
@@ -285,28 +273,31 @@ def _try_requires_both(
         else:
             buffer.append("requires")
 
-        buffer.append(" both {}".format(this_negatives))
+        buffer.append(f" both {this_negatives}")
         if this_line is not None:
-            buffer.append(" ({})".format(this_line))
+            buffer.append(f" ({this_line})")
 
-        buffer.append(" and {}".format(other_negatives))
+        buffer.append(f" and {other_negatives}")
 
         if other_line is not None:
-            buffer.append(" ({})".format(other_line))
+            buffer.append(f" ({other_line})")
 
         return "".join(buffer)
 
     def _try_requires_through(
-        self, other, details, this_line, other_line
-    ):  # type: (Incompatibility, dict, int, int) -> str
+        self,
+        other: Incompatibility,
+        this_line: int | None,
+        other_line: int | None,
+    ) -> str | None:
         if len(self._terms) == 1 or len(other.terms) == 1:
-            return
+            return None
 
         this_negative = self._single_term_where(lambda term: not term.is_positive())
         other_negative = other._single_term_where(lambda term: not term.is_positive())
 
         if this_negative is None and other_negative is None:
-            return
+            return None
 
         this_positive = self._single_term_where(lambda term: term.is_positive())
         other_positive = self._single_term_where(lambda term: term.is_positive())
@@ -334,14 +325,14 @@ def _try_requires_through(
             latter = self
             latter_line = this_line
         else:
-            return
+            return None
 
         prior_positives = [term for term in prior.terms if term.is_positive()]
 
         buffer = []
         if len(prior_positives) > 1:
             prior_string = " or ".join([self._terse(term) for term in prior_positives])
-            buffer.append("if {} then ".format(prior_string))
+            buffer.append(f"if {prior_string} then ")
         else:
             if isinstance(prior.cause, DependencyCause):
                 verb = "depends on"
@@ -349,12 +340,12 @@ def _try_requires_through(
                 verb = "requires"
 
             buffer.append(
-                "{} {} ".format(self._terse(prior_positives[0], allow_every=True), verb)
+                f"{self._terse(prior_positives[0], allow_every=True)} {verb} "
             )
 
         buffer.append(self._terse(prior_negative))
         if prior_line is not None:
-            buffer.append(" ({})".format(prior_line))
+            buffer.append(f" ({prior_line})")
 
         buffer.append(" which ")
 
@@ -370,13 +361,16 @@ def _try_requires_through(
         )
 
         if latter_line is not None:
-            buffer.append(" ({})".format(latter_line))
+            buffer.append(f" ({latter_line})")
 
         return "".join(buffer)
 
     def _try_requires_forbidden(
-        self, other, details, this_line, other_line
-    ):  # type: (Incompatibility, dict, int, int) -> str
+        self,
+        other: Incompatibility,
+        this_line: int | None,
+        other_line: int | None,
+    ) -> str | None:
         if len(self._terms) != 1 and len(other.terms) != 1:
             return None
 
@@ -393,17 +387,17 @@ def _try_requires_forbidden(
 
         negative = prior._single_term_where(lambda term: not term.is_positive())
         if negative is None:
-            return
+            return None
 
         if not negative.inverse.satisfies(latter.terms[0]):
-            return
+            return None
 
         positives = [t for t in prior.terms if t.is_positive()]
 
         buffer = []
         if len(positives) > 1:
             prior_string = " or ".join([self._terse(term) for term in positives])
-            buffer.append("if {} then ".format(prior_string))
+            buffer.append(f"if {prior_string} then ")
         else:
             buffer.append(self._terse(positives[0], allow_every=True))
             if isinstance(prior.cause, DependencyCause):
@@ -413,46 +407,45 @@ def _try_requires_forbidden(
 
         buffer.append(self._terse(latter.terms[0]) + " ")
         if prior_line is not None:
-            buffer.append("({}) ".format(prior_line))
+            buffer.append(f"({prior_line}) ")
 
         if isinstance(latter.cause, PythonCause):
-            cause = latter.cause  # type: PythonCause
-            buffer.append("which requires Python {}".format(cause.python_version))
+            cause: PythonCause = latter.cause
+            buffer.append(f"which requires Python {cause.python_version}")
         elif isinstance(latter.cause, NoVersionsCause):
             buffer.append("which doesn't match any versions")
-        elif isinstance(latter.cause, PackageNotFoundCause):
-            buffer.append("which doesn't exist")
         else:
             buffer.append("which is forbidden")
 
         if latter_line is not None:
-            buffer.append(" ({})".format(latter_line))
+            buffer.append(f" ({latter_line})")
 
         return "".join(buffer)
 
-    def _terse(self, term, allow_every=False):
+    def _terse(self, term: Term, allow_every: bool = False) -> str:
         if allow_every and term.constraint.is_any():
-            return "every version of {}".format(term.dependency.complete_name)
+            return f"every version of {term.dependency.complete_name}"
 
         if term.dependency.is_root:
-            return term.dependency.pretty_name
+            pretty_name: str = term.dependency.pretty_name
+            return pretty_name
 
-        return "{} ({})".format(
-            term.dependency.pretty_name, term.dependency.pretty_constraint
-        )
+        if term.dependency.source_type:
+            return str(term.dependency)
+        return f"{term.dependency.pretty_name} ({term.dependency.pretty_constraint})"
 
-    def _single_term_where(self, callable):  # type: (callable) -> Term
+    def _single_term_where(self, callable: Callable[[Term], bool]) -> Term | None:
         found = None
         for term in self._terms:
             if not callable(term):
                 continue
 
             if found is not None:
-                return
+                return None
 
             found = term
 
         return found
 
-    def __repr__(self):
-        return "".format(str(self))
+    def __repr__(self) -> str:
+        return f""
diff --git a/conda_lock/_vendor/poetry/mixology/incompatibility_cause.py b/conda_lock/_vendor/poetry/mixology/incompatibility_cause.py
index 8156b4fa4..b1e21429c 100644
--- a/conda_lock/_vendor/poetry/mixology/incompatibility_cause.py
+++ b/conda_lock/_vendor/poetry/mixology/incompatibility_cause.py
@@ -1,3 +1,12 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.mixology.incompatibility import Incompatibility
+
+
 class IncompatibilityCause(Exception):
     """
     The reason and Incompatibility's terms are incompatible.
@@ -5,17 +14,14 @@ class IncompatibilityCause(Exception):
 
 
 class RootCause(IncompatibilityCause):
-
     pass
 
 
 class NoVersionsCause(IncompatibilityCause):
-
     pass
 
 
 class DependencyCause(IncompatibilityCause):
-
     pass
 
 
@@ -25,19 +31,19 @@ class ConflictCause(IncompatibilityCause):
     during conflict resolution.
     """
 
-    def __init__(self, conflict, other):
+    def __init__(self, conflict: Incompatibility, other: Incompatibility) -> None:
         self._conflict = conflict
         self._other = other
 
     @property
-    def conflict(self):
+    def conflict(self) -> Incompatibility:
         return self._conflict
 
     @property
-    def other(self):
+    def other(self) -> Incompatibility:
         return self._other
 
-    def __str__(self):
+    def __str__(self) -> str:
         return str(self._conflict)
 
 
@@ -48,16 +54,16 @@ class PythonCause(IncompatibilityCause):
     with the current python version.
     """
 
-    def __init__(self, python_version, root_python_version):
+    def __init__(self, python_version: str, root_python_version: str) -> None:
         self._python_version = python_version
         self._root_python_version = root_python_version
 
     @property
-    def python_version(self):
+    def python_version(self) -> str:
         return self._python_version
 
     @property
-    def root_python_version(self):
+    def root_python_version(self) -> str:
         return self._root_python_version
 
 
@@ -67,23 +73,9 @@ class PlatformCause(IncompatibilityCause):
     (OS most likely) being incompatible with the current platform.
     """
 
-    def __init__(self, platform):
+    def __init__(self, platform: str) -> None:
         self._platform = platform
 
     @property
-    def platform(self):
+    def platform(self) -> str:
         return self._platform
-
-
-class PackageNotFoundCause(IncompatibilityCause):
-    """
-    The incompatibility represents a package that couldn't be found by its
-    source.
-    """
-
-    def __init__(self, error):
-        self._error = error
-
-    @property
-    def error(self):
-        return self._error
diff --git a/conda_lock/_vendor/poetry/mixology/partial_solution.py b/conda_lock/_vendor/poetry/mixology/partial_solution.py
old mode 100755
new mode 100644
index 175655839..3c147ebd1
--- a/conda_lock/_vendor/poetry/mixology/partial_solution.py
+++ b/conda_lock/_vendor/poetry/mixology/partial_solution.py
@@ -1,14 +1,17 @@
-from collections import OrderedDict
-from typing import Dict
-from typing import List
+from __future__ import annotations
 
-from conda_lock._vendor.poetry.core.packages import Dependency
-from conda_lock._vendor.poetry.core.packages import Package
+from typing import TYPE_CHECKING
 
-from .assignment import Assignment
-from .incompatibility import Incompatibility
-from .set_relation import SetRelation
-from .term import Term
+from conda_lock._vendor.poetry.mixology.assignment import Assignment
+from conda_lock._vendor.poetry.mixology.set_relation import SetRelation
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.mixology.incompatibility import Incompatibility
+    from conda_lock._vendor.poetry.mixology.term import Term
 
 
 class PartialSolution:
@@ -17,22 +20,23 @@ class PartialSolution:
     # what's true for the eventual set of package versions that will comprise the
     # total solution.
     #
-    # See https://github.com/dart-lang/mixology/tree/master/doc/solver.md#partial-solution.
+    # See:
+    # https://github.com/dart-lang/mixology/tree/master/doc/solver.md#partial-solution.
     """
 
-    def __init__(self):
+    def __init__(self) -> None:
         # The assignments that have been made so far, in the order they were
         # assigned.
-        self._assignments = []  # type: List[Assignment]
+        self._assignments: list[Assignment] = []
 
         # The decisions made for each package.
-        self._decisions = OrderedDict()  # type: Dict[str, Package]
+        self._decisions: dict[str, Package] = {}
 
         # The intersection of all positive Assignments for each package, minus any
         # negative Assignments that refer to that package.
         #
         # This is derived from self._assignments.
-        self._positive = OrderedDict()  # type: Dict[str, Term]
+        self._positive: dict[str, Term] = {}
 
         # The union of all negative Assignments for each package.
         #
@@ -40,7 +44,7 @@ def __init__(self):
         # map.
         #
         # This is derived from self._assignments.
-        self._negative = OrderedDict()  # type: Dict[str, Dict[str, Term]]
+        self._negative: dict[str, Term] = {}
 
         # The number of distinct solutions that have been attempted so far.
         self._attempted_solutions = 1
@@ -49,26 +53,26 @@ def __init__(self):
         self._backtracking = False
 
     @property
-    def decisions(self):  # type: () -> List[Package]
+    def decisions(self) -> list[Package]:
         return list(self._decisions.values())
 
     @property
-    def decision_level(self):  # type: () -> int
+    def decision_level(self) -> int:
         return len(self._decisions)
 
     @property
-    def attempted_solutions(self):  # type: () -> int
+    def attempted_solutions(self) -> int:
         return self._attempted_solutions
 
     @property
-    def unsatisfied(self):  # type: () -> List[Dependency]
+    def unsatisfied(self) -> list[Dependency]:
         return [
             term.dependency
             for term in self._positive.values()
             if term.dependency.complete_name not in self._decisions
         ]
 
-    def decide(self, package):  # type: (Package) -> None
+    def decide(self, package: Package) -> None:
         """
         Adds an assignment of package as a decision
         and increments the decision level.
@@ -88,8 +92,8 @@ def decide(self, package):  # type: (Package) -> None
         )
 
     def derive(
-        self, dependency, is_positive, cause
-    ):  # type: (Dependency, bool, Incompatibility) -> None
+        self, dependency: Dependency, is_positive: bool, cause: Incompatibility
+    ) -> None:
         """
         Adds an assignment of package as a derivation.
         """
@@ -103,14 +107,14 @@ def derive(
             )
         )
 
-    def _assign(self, assignment):  # type: (Assignment) -> None
+    def _assign(self, assignment: Assignment) -> None:
         """
         Adds an Assignment to _assignments and _positive or _negative.
         """
         self._assignments.append(assignment)
         self._register(assignment)
 
-    def backtrack(self, decision_level):  # type: (int) -> None
+    def backtrack(self, decision_level: int) -> None:
         """
         Resets the current decision level to decision_level, and removes all
         assignments made after that level.
@@ -136,24 +140,24 @@ def backtrack(self, decision_level):  # type: (int) -> None
             if assignment.dependency.complete_name in packages:
                 self._register(assignment)
 
-    def _register(self, assignment):  # type: (Assignment) -> None
+    def _register(self, assignment: Assignment) -> None:
         """
         Registers an Assignment in _positive or _negative.
         """
         name = assignment.dependency.complete_name
         old_positive = self._positive.get(name)
         if old_positive is not None:
-            self._positive[name] = old_positive.intersect(assignment)
+            value = old_positive.intersect(assignment)
+            assert value is not None
+            self._positive[name] = value
 
             return
 
-        ref = assignment.dependency.complete_name
-        negative_by_ref = self._negative.get(name)
-        old_negative = None if negative_by_ref is None else negative_by_ref.get(ref)
-        if old_negative is None:
-            term = assignment
-        else:
-            term = assignment.intersect(old_negative)
+        old_negative = self._negative.get(name)
+        term = (
+            assignment if old_negative is None else assignment.intersect(old_negative)
+        )
+        assert term is not None
 
         if term.is_positive():
             if name in self._negative:
@@ -161,17 +165,14 @@ def _register(self, assignment):  # type: (Assignment) -> None
 
             self._positive[name] = term
         else:
-            if name not in self._negative:
-                self._negative[name] = {}
-
-            self._negative[name][ref] = term
+            self._negative[name] = term
 
-    def satisfier(self, term):  # type: (Term) -> Assignment
+    def satisfier(self, term: Term) -> Assignment:
         """
         Returns the first Assignment in this solution such that the sublist of
         assignments up to and including that entry collectively satisfies term.
         """
-        assigned_term = None  # type: Term
+        assigned_term = None
 
         for assignment in self._assignments:
             if assignment.dependency.complete_name != term.dependency.complete_name:
@@ -197,21 +198,17 @@ def satisfier(self, term):  # type: (Term) -> Assignment
             if assigned_term.satisfies(term):
                 return assignment
 
-        raise RuntimeError("[BUG] {} is not satisfied.".format(term))
+        raise RuntimeError(f"[BUG] {term} is not satisfied.")
 
-    def satisfies(self, term):  # type: (Term) -> bool
+    def satisfies(self, term: Term) -> bool:
         return self.relation(term) == SetRelation.SUBSET
 
-    def relation(self, term):  # type: (Term) -> int
+    def relation(self, term: Term) -> str:
         positive = self._positive.get(term.dependency.complete_name)
         if positive is not None:
             return positive.relation(term)
 
-        by_ref = self._negative.get(term.dependency.complete_name)
-        if by_ref is None:
-            return SetRelation.OVERLAPPING
-
-        negative = by_ref[term.dependency.complete_name]
+        negative = self._negative.get(term.dependency.complete_name)
         if negative is None:
             return SetRelation.OVERLAPPING
 
diff --git a/conda_lock/_vendor/poetry/mixology/result.py b/conda_lock/_vendor/poetry/mixology/result.py
index 5eadeb75d..44ecafdcf 100644
--- a/conda_lock/_vendor/poetry/mixology/result.py
+++ b/conda_lock/_vendor/poetry/mixology/result.py
@@ -1,13 +1,28 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
+
+
 class SolverResult:
-    def __init__(self, root, packages, attempted_solutions):
+    def __init__(
+        self,
+        root: ProjectPackage,
+        packages: list[Package],
+        attempted_solutions: int,
+    ) -> None:
         self._root = root
         self._packages = packages
         self._attempted_solutions = attempted_solutions
 
     @property
-    def packages(self):
+    def packages(self) -> list[Package]:
         return self._packages
 
     @property
-    def attempted_solutions(self):
+    def attempted_solutions(self) -> int:
         return self._attempted_solutions
diff --git a/conda_lock/_vendor/poetry/mixology/set_relation.py b/conda_lock/_vendor/poetry/mixology/set_relation.py
index 4bd333bc0..a71e82619 100644
--- a/conda_lock/_vendor/poetry/mixology/set_relation.py
+++ b/conda_lock/_vendor/poetry/mixology/set_relation.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
 class SetRelation:
     """
     An enum of possible relationships between two sets.
diff --git a/conda_lock/_vendor/poetry/mixology/solutions/providers/__init__.py b/conda_lock/_vendor/poetry/mixology/solutions/providers/__init__.py
index 3faec7b61..67fcb12e7 100644
--- a/conda_lock/_vendor/poetry/mixology/solutions/providers/__init__.py
+++ b/conda_lock/_vendor/poetry/mixology/solutions/providers/__init__.py
@@ -1 +1,8 @@
-from .python_requirement_solution_provider import PythonRequirementSolutionProvider
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.mixology.solutions.providers.python_requirement_solution_provider import (
+    PythonRequirementSolutionProvider,
+)
+
+
+__all__ = ["PythonRequirementSolutionProvider"]
diff --git a/conda_lock/_vendor/poetry/mixology/solutions/providers/python_requirement_solution_provider.py b/conda_lock/_vendor/poetry/mixology/solutions/providers/python_requirement_solution_provider.py
index 5c490b03d..6eb5c59c0 100644
--- a/conda_lock/_vendor/poetry/mixology/solutions/providers/python_requirement_solution_provider.py
+++ b/conda_lock/_vendor/poetry/mixology/solutions/providers/python_requirement_solution_provider.py
@@ -1,15 +1,20 @@
+from __future__ import annotations
+
 import re
 
-from typing import List
+from typing import TYPE_CHECKING
 
 from crashtest.contracts.has_solutions_for_exception import HasSolutionsForException
-from crashtest.contracts.solution import Solution
 
+from conda_lock._vendor.poetry.puzzle.exceptions import SolverProblemError
+
+
+if TYPE_CHECKING:
+    from crashtest.contracts.solution import Solution
 
-class PythonRequirementSolutionProvider(HasSolutionsForException):
-    def can_solve(self, exception):  # type: (Exception) -> bool
-        from conda_lock._vendor.poetry.puzzle.exceptions import SolverProblemError
 
+class PythonRequirementSolutionProvider(HasSolutionsForException):
+    def can_solve(self, exception: Exception) -> bool:
         if not isinstance(exception, SolverProblemError):
             return False
 
@@ -19,12 +24,12 @@ def can_solve(self, exception):  # type: (Exception) -> bool
             str(exception),
         )
 
-        if not m:
-            return False
-
-        return True
+        return bool(m)
 
-    def get_solutions(self, exception):  # type: (Exception) -> List[Solution]
-        from ..solutions.python_requirement_solution import PythonRequirementSolution
+    def get_solutions(self, exception: Exception) -> list[Solution]:
+        from conda_lock._vendor.poetry.mixology.solutions.solutions.python_requirement_solution import (
+            PythonRequirementSolution,
+        )
 
+        assert isinstance(exception, SolverProblemError)
         return [PythonRequirementSolution(exception)]
diff --git a/conda_lock/_vendor/poetry/mixology/solutions/solutions/__init__.py b/conda_lock/_vendor/poetry/mixology/solutions/solutions/__init__.py
index 838e77b01..49196d7a9 100644
--- a/conda_lock/_vendor/poetry/mixology/solutions/solutions/__init__.py
+++ b/conda_lock/_vendor/poetry/mixology/solutions/solutions/__init__.py
@@ -1 +1,8 @@
-from .python_requirement_solution import PythonRequirementSolution
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.mixology.solutions.solutions.python_requirement_solution import (
+    PythonRequirementSolution,
+)
+
+
+__all__ = ["PythonRequirementSolution"]
diff --git a/conda_lock/_vendor/poetry/mixology/solutions/solutions/python_requirement_solution.py b/conda_lock/_vendor/poetry/mixology/solutions/solutions/python_requirement_solution.py
index 7075b0943..aa9adb752 100644
--- a/conda_lock/_vendor/poetry/mixology/solutions/solutions/python_requirement_solution.py
+++ b/conda_lock/_vendor/poetry/mixology/solutions/solutions/python_requirement_solution.py
@@ -1,14 +1,24 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
 from crashtest.contracts.solution import Solution
 
 
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.mixology.failure import SolveFailure
+    from conda_lock._vendor.poetry.puzzle.exceptions import SolverProblemError
+
+
 class PythonRequirementSolution(Solution):
-    def __init__(self, exception):
-        from conda_lock._vendor.poetry.core.semver import parse_constraint
+    def __init__(self, exception: SolverProblemError) -> None:
+        from conda_lock._vendor.poetry.core.constraints.version import parse_constraint
+
         from conda_lock._vendor.poetry.mixology.incompatibility_cause import PythonCause
 
         self._title = "Check your dependencies Python requirement."
 
-        failure = exception.error
+        failure: SolveFailure = exception.error
         version_solutions = []
         for incompatibility in failure._incompatibility.external_incompatibilities:
             if isinstance(incompatibility.cause, PythonCause):
@@ -18,16 +28,17 @@ def __init__(self, exception):
                 constraint = parse_constraint(incompatibility.cause.python_version)
 
                 version_solutions.append(
-                    "For {}, a possible solution would be "
-                    'to set the `python` property to "{}"'.format(
-                        incompatibility.terms[0].dependency.name,
-                        root_constraint.intersect(constraint),
-                    )
+                    "For "
+                    f"{incompatibility.terms[0].dependency.name},"
+                    " a possible solution would be to set the"
+                    " `python` property to"
+                    f' "{root_constraint.intersect(constraint)}"'
                 )
 
         description = (
-            "The Python requirement can be specified via the `python` "
-            "or `markers` properties"
+            "The Python requirement can be specified via the"
+            " `python` or"
+            " `markers` properties"
         )
         if version_solutions:
             description += "\n\n" + "\n".join(version_solutions)
@@ -41,12 +52,12 @@ def solution_title(self) -> str:
         return self._title
 
     @property
-    def solution_description(self):
+    def solution_description(self) -> str:
         return self._description
 
     @property
-    def documentation_links(self):
+    def documentation_links(self) -> list[str]:
         return [
-            "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies",
-            "https://python-poetry.org/docs/dependency-specification/#using-environment-markers",
+            "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies",  # noqa: E501
+            "https://python-poetry.org/docs/dependency-specification/#using-environment-markers",  # noqa: E501
         ]
diff --git a/conda_lock/_vendor/poetry/mixology/term.py b/conda_lock/_vendor/poetry/mixology/term.py
old mode 100755
new mode 100644
index 37cbced9c..1810f696e
--- a/conda_lock/_vendor/poetry/mixology/term.py
+++ b/conda_lock/_vendor/poetry/mixology/term.py
@@ -1,12 +1,18 @@
-# -*- coding: utf-8 -*-
-from typing import Union
+from __future__ import annotations
 
-from conda_lock._vendor.poetry.core.packages import Dependency
+import functools
 
-from .set_relation import SetRelation
+from typing import TYPE_CHECKING
 
+from conda_lock._vendor.poetry.mixology.set_relation import SetRelation
 
-class Term(object):
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+
+
+class Term:
     """
     A statement about a package which is true or false for a given selection of
     package versions.
@@ -14,26 +20,28 @@ class Term(object):
     See https://github.com/dart-lang/pub/tree/master/doc/solver.md#term.
     """
 
-    def __init__(self, dependency, is_positive):  # type: (Dependency, bool)  -> None
+    def __init__(self, dependency: Dependency, is_positive: bool) -> None:
         self._dependency = dependency
         self._positive = is_positive
+        self.relation = functools.lru_cache(maxsize=None)(self._relation)
+        self.intersect = functools.lru_cache(maxsize=None)(self._intersect)
 
     @property
-    def inverse(self):  # type: () -> Term
+    def inverse(self) -> Term:
         return Term(self._dependency, not self.is_positive())
 
     @property
-    def dependency(self):
+    def dependency(self) -> Dependency:
         return self._dependency
 
     @property
-    def constraint(self):
+    def constraint(self) -> VersionConstraint:
         return self._dependency.constraint
 
-    def is_positive(self):  # type: () -> bool
+    def is_positive(self) -> bool:
         return self._positive
 
-    def satisfies(self, other):  # type: (Term) -> bool
+    def satisfies(self, other: Term) -> bool:
         """
         Returns whether this term satisfies another.
         """
@@ -42,15 +50,13 @@ def satisfies(self, other):  # type: (Term) -> bool
             and self.relation(other) == SetRelation.SUBSET
         )
 
-    def relation(self, other):  # type: (Term) -> int
+    def _relation(self, other: Term) -> str:
         """
         Returns the relationship between the package versions
         allowed by this term and another.
         """
         if self.dependency.complete_name != other.dependency.complete_name:
-            raise ValueError(
-                "{} should refer to {}".format(other, self.dependency.complete_name)
-            )
+            raise ValueError(f"{other} should refer to {self.dependency.complete_name}")
 
         other_constraint = other.constraint
 
@@ -106,15 +112,13 @@ def relation(self, other):  # type: (Term) -> int
                 # not foo ^1.5.0 is a superset of not foo ^1.0.0
                 return SetRelation.OVERLAPPING
 
-    def intersect(self, other):  # type: (Term) -> Union[Term, None]
+    def _intersect(self, other: Term) -> Term | None:
         """
         Returns a Term that represents the packages
         allowed by both this term and another
         """
         if self.dependency.complete_name != other.dependency.complete_name:
-            raise ValueError(
-                "{} should refer to {}".format(other, self.dependency.complete_name)
-            )
+            raise ValueError(f"{other} should refer to {self.dependency.complete_name}")
 
         if self._compatible_dependency(other.dependency):
             if self.is_positive() != other.is_positive():
@@ -123,49 +127,61 @@ def intersect(self, other):  # type: (Term) -> Union[Term, None]
                 negative = other if self.is_positive() else self
 
                 return self._non_empty_term(
-                    positive.constraint.difference(negative.constraint), True
+                    positive.constraint.difference(negative.constraint), True, other
                 )
             elif self.is_positive():
                 # foo ^1.0.0 ∩ foo >=1.5.0 <3.0.0 → foo ^1.5.0
                 return self._non_empty_term(
-                    self.constraint.intersect(other.constraint), True
+                    self.constraint.intersect(other.constraint), True, other
                 )
             else:
                 # not foo ^1.0.0 ∩ not foo >=1.5.0 <3.0.0 → not foo >=1.0.0 <3.0.0
                 return self._non_empty_term(
-                    self.constraint.union(other.constraint), False
+                    self.constraint.union(other.constraint), False, other
                 )
         elif self.is_positive() != other.is_positive():
             return self if self.is_positive() else other
         else:
-            return
+            return None
 
-    def difference(self, other):  # type: (Term) -> Term
+    def difference(self, other: Term) -> Term | None:
         """
         Returns a Term that represents packages
         allowed by this term and not by the other
         """
         return self.intersect(other.inverse)
 
-    def _compatible_dependency(self, other):
+    def _compatible_dependency(self, other: Dependency) -> bool:
         return (
             self.dependency.is_root
             or other.is_root
             or other.is_same_package_as(self.dependency)
+            or (
+                # we do this here to indicate direct origin dependencies are
+                # compatible with NVR dependencies
+                self.dependency.complete_name == other.complete_name
+                and self.dependency.is_direct_origin() != other.is_direct_origin()
+            )
         )
 
-    def _non_empty_term(self, constraint, is_positive):
+    def _non_empty_term(
+        self, constraint: VersionConstraint, is_positive: bool, other: Term
+    ) -> Term | None:
         if constraint.is_empty():
-            return
-
-        return Term(self.dependency.with_constraint(constraint), is_positive)
-
-    def __str__(self):
-        return "{} {} ({})".format(
-            "not " if not self.is_positive() else "",
-            self._dependency.pretty_name,
-            self._dependency.pretty_constraint,
+            return None
+
+        # when creating a new term prefer direct-reference dependencies
+        dependency = (
+            other.dependency
+            if not self.dependency.is_direct_origin()
+            and other.dependency.is_direct_origin()
+            else self.dependency
         )
+        return Term(dependency.with_constraint(constraint), is_positive)
+
+    def __str__(self) -> str:
+        prefix = "not " if not self.is_positive() else ""
+        return f"{prefix}{self._dependency}"
 
-    def __repr__(self):
-        return "".format(str(self))
+    def __repr__(self) -> str:
+        return f""
diff --git a/conda_lock/_vendor/poetry/mixology/version_solver.py b/conda_lock/_vendor/poetry/mixology/version_solver.py
old mode 100755
new mode 100644
index afbbbdcb4..a04b56983
--- a/conda_lock/_vendor/poetry/mixology/version_solver.py
+++ b/conda_lock/_vendor/poetry/mixology/version_solver.py
@@ -1,37 +1,76 @@
-# -*- coding: utf-8 -*-
+from __future__ import annotations
+
+import functools
 import time
 
 from typing import TYPE_CHECKING
-from typing import Any
-from typing import Dict
-from typing import List
-from typing import Union
-
-from conda_lock._vendor.poetry.core.packages import Dependency
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.packages import ProjectPackage
-from conda_lock._vendor.poetry.core.semver import Version
-from conda_lock._vendor.poetry.core.semver import VersionRange
-
-from .failure import SolveFailure
-from .incompatibility import Incompatibility
-from .incompatibility_cause import ConflictCause
-from .incompatibility_cause import NoVersionsCause
-from .incompatibility_cause import PackageNotFoundCause
-from .incompatibility_cause import RootCause
-from .partial_solution import PartialSolution
-from .result import SolverResult
-from .set_relation import SetRelation
-from .term import Term
+
+from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+
+from conda_lock._vendor.poetry.mixology.failure import SolveFailure
+from conda_lock._vendor.poetry.mixology.incompatibility import Incompatibility
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import ConflictCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import NoVersionsCause
+from conda_lock._vendor.poetry.mixology.incompatibility_cause import RootCause
+from conda_lock._vendor.poetry.mixology.partial_solution import PartialSolution
+from conda_lock._vendor.poetry.mixology.result import SolverResult
+from conda_lock._vendor.poetry.mixology.set_relation import SetRelation
+from conda_lock._vendor.poetry.mixology.term import Term
 
 
 if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
+
+    from conda_lock._vendor.poetry.packages import DependencyPackage
     from conda_lock._vendor.poetry.puzzle.provider import Provider
 
 
 _conflict = object()
 
 
+class DependencyCache:
+    """
+    A cache of the valid dependencies.
+
+    The key observation here is that during the search - except at backtracking
+    - once we have decided that a dependency is invalid, we never need check it
+    again.
+    """
+
+    def __init__(self, provider: Provider) -> None:
+        self.provider = provider
+        self.cache: dict[
+            tuple[str, str | None, str | None, str | None, str | None],
+            list[DependencyPackage],
+        ] = {}
+
+        self.search_for = functools.lru_cache(maxsize=128)(self._search_for)
+
+    def _search_for(self, dependency: Dependency) -> list[DependencyPackage]:
+        key = (
+            dependency.complete_name,
+            dependency.source_type,
+            dependency.source_url,
+            dependency.source_reference,
+            dependency.source_subdirectory,
+        )
+
+        packages = self.cache.get(key)
+        if packages is None:
+            packages = self.provider.search_for(dependency)
+        else:
+            packages = [
+                p for p in packages if dependency.constraint.allows(p.package.version)
+            ]
+
+        self.cache[key] = packages
+
+        return packages
+
+    def clear(self) -> None:
+        self.cache.clear()
+
+
 class VersionSolver:
     """
     The version solver that finds a set of package versions that satisfy the
@@ -41,30 +80,19 @@ class VersionSolver:
     on how this solver works.
     """
 
-    def __init__(
-        self,
-        root,  # type: ProjectPackage
-        provider,  # type: Provider
-        locked=None,  # type: Dict[str, Package]
-        use_latest=None,  # type: List[str]
-    ):
+    def __init__(self, root: ProjectPackage, provider: Provider) -> None:
         self._root = root
         self._provider = provider
-        self._locked = locked or {}
-
-        if use_latest is None:
-            use_latest = []
-
-        self._use_latest = use_latest
-
-        self._incompatibilities = {}  # type: Dict[str, List[Incompatibility]]
+        self._dependency_cache = DependencyCache(provider)
+        self._incompatibilities: dict[str, list[Incompatibility]] = {}
+        self._contradicted_incompatibilities: set[Incompatibility] = set()
         self._solution = PartialSolution()
 
     @property
-    def solution(self):  # type: () -> PartialSolution
+    def solution(self) -> PartialSolution:
         return self._solution
 
-    def solve(self):  # type: () -> SolverResult
+    def solve(self) -> SolverResult:
         """
         Finds a set of dependencies that match the root package's constraints,
         or raises an error if no such set is available.
@@ -78,7 +106,7 @@ def solve(self):  # type: () -> SolverResult
         )
 
         try:
-            next = self._root.name
+            next: str | None = self._root.name
             while next is not None:
                 self._propagate(next)
                 next = self._choose_package_version()
@@ -88,20 +116,16 @@ def solve(self):  # type: () -> SolverResult
             raise
         finally:
             self._log(
-                "Version solving took {:.3f} seconds.\n"
-                "Tried {} solutions.".format(
-                    time.time() - start, self._solution.attempted_solutions
-                )
+                f"Version solving took {time.time() - start:.3f} seconds.\n"
+                f"Tried {self._solution.attempted_solutions} solutions."
             )
 
-    def _propagate(self, package):  # type: (str) -> None
+    def _propagate(self, package: str) -> None:
         """
         Performs unit propagation on incompatibilities transitively
         related to package to derive new assignments for _solution.
         """
-        changed = set()
-        changed.add(package)
-
+        changed = {package}
         while changed:
             package = changed.pop()
 
@@ -110,12 +134,15 @@ def _propagate(self, package):  # type: (str) -> None
             # we can derive stronger assignments sooner and more eagerly find
             # conflicts.
             for incompatibility in reversed(self._incompatibilities[package]):
+                if incompatibility in self._contradicted_incompatibilities:
+                    continue
+
                 result = self._propagate_incompatibility(incompatibility)
 
                 if result is _conflict:
                     # If the incompatibility is satisfied by the solution, we use
-                    # _resolve_conflict() to determine the root cause of the conflict as a
-                    # new incompatibility.
+                    # _resolve_conflict() to determine the root cause of the conflict as
+                    # a new incompatibility.
                     #
                     # It also backjumps to a point in the solution
                     # where that incompatibility will allow us to derive new assignments
@@ -129,11 +156,11 @@ def _propagate(self, package):  # type: (str) -> None
                     changed.add(str(self._propagate_incompatibility(root_cause)))
                     break
                 elif result is not None:
-                    changed.add(result)
+                    changed.add(str(result))
 
     def _propagate_incompatibility(
-        self, incompatibility
-    ):  # type: (Incompatibility) -> Union[str, _conflict, None]
+        self, incompatibility: Incompatibility
+    ) -> str | object | None:
         """
         If incompatibility is almost satisfied by _solution, adds the
         negation of the unsatisfied term to _solution.
@@ -156,12 +183,13 @@ def _propagate_incompatibility(
                 # If term is already contradicted by _solution, then
                 # incompatibility is contradicted as well and there's nothing new we
                 # can deduce from it.
-                return
+                self._contradicted_incompatibilities.add(incompatibility)
+                return None
             elif relation == SetRelation.OVERLAPPING:
                 # If more than one term is inconclusive, we can't deduce anything about
                 # incompatibility.
                 if unsatisfied is not None:
-                    return
+                    return None
 
                 # If exactly one term in incompatibility is inconclusive, then it's
                 # almost satisfied and [term] is the unsatisfied term. We can add the
@@ -173,32 +201,31 @@ def _propagate_incompatibility(
         if unsatisfied is None:
             return _conflict
 
-        self._log(
-            "derived: {}{}".format(
-                "not " if unsatisfied.is_positive() else "", unsatisfied.dependency
-            )
-        )
+        self._contradicted_incompatibilities.add(incompatibility)
+
+        adverb = "not " if unsatisfied.is_positive() else ""
+        self._log(f"derived: {adverb}{unsatisfied.dependency}")
 
         self._solution.derive(
             unsatisfied.dependency, not unsatisfied.is_positive(), incompatibility
         )
 
-        return unsatisfied.dependency.complete_name
+        complete_name: str = unsatisfied.dependency.complete_name
+        return complete_name
 
-    def _resolve_conflict(
-        self, incompatibility
-    ):  # type: (Incompatibility) -> Incompatibility
+    def _resolve_conflict(self, incompatibility: Incompatibility) -> Incompatibility:
         """
         Given an incompatibility that's satisfied by _solution,
-        The `conflict resolution`_ constructs a new incompatibility that encapsulates the root
-        cause of the conflict and backtracks _solution until the new
+        The `conflict resolution`_ constructs a new incompatibility that encapsulates
+        the root cause of the conflict and backtracks _solution until the new
         incompatibility will allow _propagate() to deduce new assignments.
 
         Adds the new incompatibility to _incompatibilities and returns it.
 
-        .. _conflict resolution: https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution
+        .. _conflict resolution:
+        https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution
         """
-        self._log("conflict: {}".format(incompatibility))
+        self._log(f"conflict: {incompatibility}")
 
         new_incompatibility = False
         while not incompatibility.is_failure():
@@ -261,11 +288,16 @@ def _resolve_conflict(
             # than a derivation), then incompatibility is the root cause. We then
             # backjump to previous_satisfier_level, where incompatibility is
             # guaranteed to allow _propagate to produce more assignments.
+
+            # using assert to suppress mypy [union-attr]
+            assert most_recent_satisfier is not None
             if (
                 previous_satisfier_level < most_recent_satisfier.decision_level
                 or most_recent_satisfier.cause is None
             ):
                 self._solution.backtrack(previous_satisfier_level)
+                self._contradicted_incompatibilities.clear()
+                self._dependency_cache.clear()
                 if new_incompatibility:
                     self._add_incompatibility(incompatibility)
 
@@ -277,10 +309,9 @@ def _resolve_conflict(
             # true (that is, we know for sure no solution will satisfy the
             # incompatibility) while also approximating the intuitive notion of the
             # "root cause" of the conflict.
-            new_terms = []
-            for term in incompatibility.terms:
-                if term != most_recent_term:
-                    new_terms.append(term)
+            new_terms = [
+                term for term in incompatibility.terms if term != most_recent_term
+            ]
 
             for term in most_recent_satisfier.cause.terms:
                 if term.dependency != most_recent_satisfier.dependency:
@@ -297,7 +328,8 @@ def _resolve_conflict(
             # the incompatibility as well, See the `algorithm documentation`_ for
             # details.
             #
-            # .. _algorithm documentation: https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution
+            # .. _algorithm documentation:
+            # https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution  # noqa: E501
             if difference is not None:
                 new_terms.append(difference.inverse)
 
@@ -307,20 +339,16 @@ def _resolve_conflict(
             new_incompatibility = True
 
             partially = "" if difference is None else " partially"
-            bang = "!"
-            self._log(
-                "{} {} is{} satisfied by {}".format(
-                    bang, most_recent_term, partially, most_recent_satisfier
-                )
-            )
             self._log(
-                '{} which is caused by "{}"'.format(bang, most_recent_satisfier.cause)
+                f"! {most_recent_term} is{partially} satisfied by"
+                f" {most_recent_satisfier}"
             )
-            self._log("{} thus: {}".format(bang, incompatibility))
+            self._log(f'! which is caused by "{most_recent_satisfier.cause}"')
+            self._log(f"! thus: {incompatibility}")
 
         raise SolveFailure(incompatibility)
 
-    def _choose_package_version(self):  # type: () -> Union[str, None]
+    def _choose_package_version(self) -> str | None:
         """
         Tries to select a version of a required package.
 
@@ -330,77 +358,77 @@ def _choose_package_version(self):  # type: () -> Union[str, None]
         """
         unsatisfied = self._solution.unsatisfied
         if not unsatisfied:
-            return
+            return None
 
-        # Prefer packages with as few remaining versions as possible,
-        # so that if a conflict is necessary it's forced quickly.
-        def _get_min(dependency):
-            if dependency.name in self._use_latest:
-                # If we're forced to use the latest version of a package, it effectively
-                # only has one version to choose from.
-                return not dependency.marker.is_any(), 1
-
-            locked = self._get_locked(dependency)
-            if locked and (
-                dependency.constraint.allows(locked.version)
-                or locked.is_prerelease()
-                and dependency.constraint.allows(locked.version.next_patch)
-            ):
-                return not dependency.marker.is_any(), 1
+        class Preference:
+            """
+            Preference is one of the criteria for choosing which dependency to solve
+            first. A higher value means that there are "more options" to satisfy
+            a dependency. A lower value takes precedence.
+            """
 
-            # VCS, URL, File or Directory dependencies
-            # represent a single version
-            if (
-                dependency.is_vcs()
-                or dependency.is_url()
-                or dependency.is_file()
-                or dependency.is_directory()
-            ):
-                return not dependency.marker.is_any(), 1
+            DIRECT_ORIGIN = 0
+            NO_CHOICE = 1
+            USE_LATEST = 2
+            LOCKED = 3
+            DEFAULT = 4
 
-            try:
-                return (
-                    not dependency.marker.is_any(),
-                    len(self._provider.search_for(dependency)),
-                )
-            except ValueError:
-                return not dependency.marker.is_any(), 0
+        # Prefer packages with as few remaining versions as possible,
+        # so that if a conflict is necessary it's forced quickly.
+        # In order to provide results that are as deterministic as possible
+        # and consistent between `poetry lock` and `poetry update`, the return value
+        # of two different dependencies should not be equal if possible.
+        def _get_min(dependency: Dependency) -> tuple[bool, int, int]:
+            # Direct origin dependencies must be handled first: we don't want to resolve
+            # a regular dependency for some package only to find later that we had a
+            # direct-origin dependency.
+            if dependency.is_direct_origin():
+                return False, Preference.DIRECT_ORIGIN, 1
+
+            is_specific_marker = not dependency.marker.is_any()
+
+            use_latest = dependency.name in self._provider.use_latest
+            if not use_latest:
+                locked = self._provider.get_locked(dependency)
+                if locked:
+                    return is_specific_marker, Preference.LOCKED, 1
+
+            num_packages = len(self._dependency_cache.search_for(dependency))
+
+            if num_packages < 2:
+                preference = Preference.NO_CHOICE
+            elif use_latest:
+                preference = Preference.USE_LATEST
+            else:
+                preference = Preference.DEFAULT
+            return is_specific_marker, preference, num_packages
 
         if len(unsatisfied) == 1:
             dependency = unsatisfied[0]
         else:
             dependency = min(*unsatisfied, key=_get_min)
 
-        locked = self._get_locked(dependency)
-        if locked is None or not dependency.constraint.allows(locked.version):
-            try:
-                packages = self._provider.search_for(dependency)
-            except ValueError as e:
-                self._add_incompatibility(
-                    Incompatibility([Term(dependency, True)], PackageNotFoundCause(e))
-                )
-                return dependency.complete_name
-
-            try:
-                version = packages[0]
-            except IndexError:
-                version = None
+        locked = self._provider.get_locked(dependency)
+        if locked is None:
+            packages = self._dependency_cache.search_for(dependency)
+            package = next(iter(packages), None)
 
-            if version is None:
+            if package is None:
                 # If there are no versions that satisfy the constraint,
                 # add an incompatibility that indicates that.
                 self._add_incompatibility(
                     Incompatibility([Term(dependency, True)], NoVersionsCause())
                 )
 
-                return dependency.complete_name
+                complete_name = dependency.complete_name
+                return complete_name
         else:
-            version = locked
+            package = locked
 
-        version = self._provider.complete_package(version)
+        package = self._provider.complete_package(package)
 
         conflict = False
-        for incompatibility in self._provider.incompatibilities_for(version):
+        for incompatibility in self._provider.incompatibilities_for(package):
             self._add_incompatibility(incompatibility)
 
             # If an incompatibility is already satisfied, then selecting version
@@ -409,27 +437,22 @@ def _get_min(dependency):
             # We'll continue adding its dependencies, then go back to
             # unit propagation which will guide us to choose a better version.
             conflict = conflict or all(
-                [
-                    term.dependency.complete_name == dependency.complete_name
-                    or self._solution.satisfies(term)
-                    for term in incompatibility.terms
-                ]
+                term.dependency.complete_name == dependency.complete_name
+                or self._solution.satisfies(term)
+                for term in incompatibility.terms
             )
 
         if not conflict:
-            self._solution.decide(version)
+            self._solution.decide(package.package)
             self._log(
-                "selecting {} ({})".format(
-                    version.complete_name, version.full_pretty_version
-                )
+                f"selecting {package.package.complete_name}"
+                f" ({package.package.full_pretty_version})"
             )
 
-        return dependency.complete_name
-
-    def _excludes_single_version(self, constraint):  # type: (Any) -> bool
-        return isinstance(VersionRange().difference(constraint), Version)
+        complete_name = dependency.complete_name
+        return complete_name
 
-    def _result(self):  # type: () -> SolverResult
+    def _result(self) -> SolverResult:
         """
         Creates a #SolverResult from the decisions in _solution
         """
@@ -441,8 +464,8 @@ def _result(self):  # type: () -> SolverResult
             self._solution.attempted_solutions,
         )
 
-    def _add_incompatibility(self, incompatibility):  # type: (Incompatibility) -> None
-        self._log("fact: {}".format(incompatibility))
+    def _add_incompatibility(self, incompatibility: Incompatibility) -> None:
+        self._log(f"fact: {incompatibility}")
 
         for term in incompatibility.terms:
             if term.dependency.complete_name not in self._incompatibilities:
@@ -458,18 +481,5 @@ def _add_incompatibility(self, incompatibility):  # type: (Incompatibility) -> N
                 incompatibility
             )
 
-    def _get_locked(self, dependency):  # type: (Dependency) -> Union[Package, None]
-        if dependency.name in self._use_latest:
-            return
-
-        locked = self._locked.get(dependency.name)
-        if not locked:
-            return
-
-        if not dependency.is_same_package_as(locked):
-            return
-
-        return locked
-
-    def _log(self, text):
+    def _log(self, text: str) -> None:
         self._provider.debug(text, self._solution.attempted_solutions)
diff --git a/conda_lock/_vendor/poetry/packages/__init__.py b/conda_lock/_vendor/poetry/packages/__init__.py
index 555a8317e..79f4647f3 100644
--- a/conda_lock/_vendor/poetry/packages/__init__.py
+++ b/conda_lock/_vendor/poetry/packages/__init__.py
@@ -1,3 +1,8 @@
-from .dependency_package import DependencyPackage
-from .locker import Locker
-from .package_collection import PackageCollection
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.packages.dependency_package import DependencyPackage
+from conda_lock._vendor.poetry.packages.locker import Locker
+from conda_lock._vendor.poetry.packages.package_collection import PackageCollection
+
+
+__all__ = ["DependencyPackage", "Locker", "PackageCollection"]
diff --git a/conda_lock/_vendor/poetry/packages/dependency_package.py b/conda_lock/_vendor/poetry/packages/dependency_package.py
index 9b83627e6..ef7eac5e4 100644
--- a/conda_lock/_vendor/poetry/packages/dependency_package.py
+++ b/conda_lock/_vendor/poetry/packages/dependency_package.py
@@ -1,51 +1,47 @@
-from typing import List
+from __future__ import annotations
 
-from conda_lock._vendor.poetry.core.packages.dependency import Dependency
-from conda_lock._vendor.poetry.core.packages.package import Package
+from typing import TYPE_CHECKING
 
 
-class DependencyPackage(object):
-    def __init__(self, dependency, package):  # type: (Dependency, Package) -> None
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+
+class DependencyPackage:
+    def __init__(self, dependency: Dependency, package: Package) -> None:
         self._dependency = dependency
         self._package = package
 
     @property
-    def dependency(self):  # type: () -> Dependency
+    def dependency(self) -> Dependency:
         return self._dependency
 
     @property
-    def package(self):  # type: () -> Package
+    def package(self) -> Package:
         return self._package
 
-    def clone(self):  # type: () -> DependencyPackage
+    def clone(self) -> DependencyPackage:
         return self.__class__(self._dependency, self._package.clone())
 
-    def with_features(self, features):  # type: (List[str]) -> "DependencyPackage"
+    def with_features(self, features: list[str]) -> DependencyPackage:
         return self.__class__(self._dependency, self._package.with_features(features))
 
-    def without_features(self):  # type: () -> "DependencyPackage"
+    def without_features(self) -> DependencyPackage:
         return self.with_features([])
 
-    def __getattr__(self, name):
-        return getattr(self._package, name)
-
-    def __setattr__(self, key, value):
-        if key in {"_dependency", "_package"}:
-            return super(DependencyPackage, self).__setattr__(key, value)
-
-        setattr(self._package, key, value)
-
-    def __str__(self):
+    def __str__(self) -> str:
         return str(self._package)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return repr(self._package)
 
-    def __hash__(self):
+    def __hash__(self) -> int:
         return hash(self._package)
 
-    def __eq__(self, other):
+    def __eq__(self, other: object) -> bool:
         if isinstance(other, DependencyPackage):
             other = other.package
 
-        return self._package == other
+        equal: bool = self._package == other
+        return equal
diff --git a/conda_lock/_vendor/poetry/packages/locker.py b/conda_lock/_vendor/poetry/packages/locker.py
index d5074dcbe..d0e852b56 100644
--- a/conda_lock/_vendor/poetry/packages/locker.py
+++ b/conda_lock/_vendor/poetry/packages/locker.py
@@ -1,154 +1,177 @@
+from __future__ import annotations
+
 import json
 import logging
 import os
 import re
 
-from copy import deepcopy
 from hashlib import sha256
-from typing import Dict
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Union
-
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Any
+from typing import cast
+
+from packaging.utils import canonicalize_name
+from conda_lock._vendor.poetry.core.constraints.version import Version
+from conda_lock._vendor.poetry.core.constraints.version import parse_constraint
+from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+from conda_lock._vendor.poetry.core.packages.package import Package
+from conda_lock._vendor.poetry.core.toml.file import TOMLFile
+from conda_lock._vendor.poetry.core.version.markers import parse_marker
+from conda_lock._vendor.poetry.core.version.requirements import InvalidRequirement
 from tomlkit import array
+from tomlkit import comment
 from tomlkit import document
 from tomlkit import inline_table
-from tomlkit import item
 from tomlkit import table
-from tomlkit.exceptions import TOMLKitError
 
-from conda_lock._vendor.poetry.repositories import Repository
+from conda_lock._vendor.poetry.utils._compat import tomllib
 
-from conda_lock._vendor.poetry.core.packages import dependency_from_pep_508
-from conda_lock._vendor.poetry.core.packages.dependency import Dependency
-from conda_lock._vendor.poetry.core.packages.package import Package
-from conda_lock._vendor.poetry.core.semver import parse_constraint
-from conda_lock._vendor.poetry.core.semver.version import Version
-from conda_lock._vendor.poetry.core.toml.file import TOMLFile
-from conda_lock._vendor.poetry.core.version.markers import parse_marker
-from conda_lock._vendor.poetry.core.version.requirements import InvalidRequirement
-from conda_lock._vendor.poetry.packages import DependencyPackage
-from conda_lock._vendor.poetry.utils._compat import OrderedDict
-from conda_lock._vendor.poetry.utils._compat import Path
-from conda_lock._vendor.poetry.utils.extras import get_extra_package_names
 
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency
+    from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency
+    from conda_lock._vendor.poetry.core.packages.url_dependency import URLDependency
+    from conda_lock._vendor.poetry.core.packages.vcs_dependency import VCSDependency
+    from tomlkit.toml_document import TOMLDocument
 
-logger = logging.getLogger(__name__)
+    from conda_lock._vendor.poetry.repositories.lockfile_repository import LockfileRepository
 
+logger = logging.getLogger(__name__)
+_GENERATED_IDENTIFIER = "@" + "generated"
+GENERATED_COMMENT = (
+    f"This file is automatically {_GENERATED_IDENTIFIER} by Poetry and should not be"
+    " changed by hand."
+)
 
-class Locker(object):
 
-    _VERSION = "1.1"
+class Locker:
+    _VERSION = "2.0"
+    _READ_VERSION_RANGE = ">=1,<3"
 
-    _relevant_keys = ["dependencies", "dev-dependencies", "source", "extras"]
+    _legacy_keys = ["dependencies", "source", "extras", "dev-dependencies"]
+    _relevant_keys = [*_legacy_keys, "group"]
 
-    def __init__(self, lock, local_config):  # type: (Path, dict) -> None
-        self._lock = TOMLFile(lock)
+    def __init__(self, lock: str | Path, local_config: dict[str, Any]) -> None:
+        self._lock = lock if isinstance(lock, Path) else Path(lock)
         self._local_config = local_config
-        self._lock_data = None
+        self._lock_data: dict[str, Any] | None = None
         self._content_hash = self._get_content_hash()
 
     @property
-    def lock(self):  # type: () -> TOMLFile
+    def lock(self) -> Path:
         return self._lock
 
     @property
-    def lock_data(self):
+    def lock_data(self) -> dict[str, Any]:
         if self._lock_data is None:
             self._lock_data = self._get_lock_data()
 
         return self._lock_data
 
-    def is_locked(self):  # type: () -> bool
+    def is_locked(self) -> bool:
         """
         Checks whether the locker has been locked (lockfile found).
         """
-        if not self._lock.exists():
-            return False
-
-        return "package" in self.lock_data
+        return self._lock.exists()
 
-    def is_fresh(self):  # type: () -> bool
+    def is_fresh(self) -> bool:
         """
         Checks whether the lock file is still up to date with the current hash.
         """
-        lock = self._lock.read()
+        with self.lock.open("rb") as f:
+            lock = tomllib.load(f)
         metadata = lock.get("metadata", {})
 
         if "content-hash" in metadata:
-            return self._content_hash == lock["metadata"]["content-hash"]
+            fresh: bool = self._content_hash == metadata["content-hash"]
+            return fresh
 
         return False
 
-    def locked_repository(
-        self, with_dev_reqs=False
-    ):  # type: (bool) -> Repository
+    def locked_repository(self) -> LockfileRepository:
         """
         Searches and returns a repository of locked packages.
         """
         from conda_lock._vendor.poetry.factory import Factory
+        from conda_lock._vendor.poetry.repositories.lockfile_repository import LockfileRepository
+
+        repository = LockfileRepository()
 
         if not self.is_locked():
-            return Repository()
+            return repository
 
         lock_data = self.lock_data
-        packages = Repository()
-
-        if with_dev_reqs:
-            locked_packages = lock_data["package"]
-        else:
-            locked_packages = [
-                p for p in lock_data["package"] if p["category"] == "main"
-            ]
+        locked_packages = cast("list[dict[str, Any]]", lock_data["package"])
 
         if not locked_packages:
-            return packages
+            return repository
 
         for info in locked_packages:
             source = info.get("source", {})
             source_type = source.get("type")
             url = source.get("url")
             if source_type in ["directory", "file"]:
-                url = self._lock.path.parent.joinpath(url).resolve().as_posix()
+                url = self.lock.parent.joinpath(url).resolve().as_posix()
 
+            name = info["name"]
             package = Package(
-                info["name"],
+                name,
                 info["version"],
                 info["version"],
                 source_type=source_type,
                 source_url=url,
                 source_reference=source.get("reference"),
                 source_resolved_reference=source.get("resolved_reference"),
+                source_subdirectory=source.get("subdirectory"),
             )
             package.description = info.get("description", "")
-            package.category = info["category"]
+            package.category = info.get("category", "main")
             package.optional = info["optional"]
-            if "hashes" in lock_data["metadata"]:
-                # Old lock so we create dummy files from the hashes
-                package.files = [
-                    {"name": h, "hash": h}
-                    for h in lock_data["metadata"]["hashes"][info["name"]]
-                ]
+            metadata = cast("dict[str, Any]", lock_data["metadata"])
+
+            # Storing of package files and hashes has been through a few generations in
+            # the lockfile, we can read them all:
+            #
+            # - latest and preferred is that this is read per package, from
+            #   package.files
+            # - oldest is that hashes were stored in metadata.hashes without filenames
+            # - in between those two, hashes were stored alongside filenames in
+            #   metadata.files
+            package_files = info.get("files")
+            if package_files is not None:
+                package.files = package_files
+            elif "hashes" in metadata:
+                hashes = cast("dict[str, Any]", metadata["hashes"])
+                package.files = [{"name": h, "hash": h} for h in hashes[name]]
+            elif source_type in {"git", "directory", "url"}:
+                package.files = []
             else:
-                package.files = lock_data["metadata"]["files"][info["name"]]
+                files = metadata["files"][name]
+                if source_type == "file":
+                    filename = Path(url).name
+                    package.files = [item for item in files if item["file"] == filename]
+                else:
+                    # Strictly speaking, this is not correct, but we have no chance
+                    # to always determine which are the correct files because the
+                    # lockfile doesn't keep track which files belong to which package.
+                    package.files = files
 
             package.python_versions = info["python-versions"]
             extras = info.get("extras", {})
             if extras:
                 for name, deps in extras.items():
+                    name = canonicalize_name(name)
                     package.extras[name] = []
 
                     for dep in deps:
                         try:
-                            dependency = dependency_from_pep_508(dep)
+                            dependency = Dependency.create_from_pep_508(dep)
                         except InvalidRequirement:
                             # handle lock files with invalid PEP 508
                             m = re.match(r"^(.+?)(?:\[(.+?)])?(?:\s+\((.+)\))?$", dep)
+                            if not m:
+                                raise
                             dep_name = m.group(1)
                             extras = m.group(2) or ""
                             constraint = m.group(3) or "*"
@@ -174,10 +197,11 @@ def locked_repository(
                         package.marker = parse_marker(split_dep[1].strip())
 
             for dep_name, constraint in info.get("dependencies", {}).items():
-
-                root_dir = self._lock.path.parent
+                root_dir = self.lock.parent
                 if package.source_type == "directory":
-                    # root dir should be the source of the package relative to the lock path
+                    # root dir should be the source of the package relative to the lock
+                    # path
+                    assert package.source_url is not None
                     root_dir = Path(package.source_url)
 
                 if isinstance(constraint, list):
@@ -195,217 +219,28 @@ def locked_repository(
             if "develop" in info:
                 package.develop = info["develop"]
 
-            packages.add_package(package)
-
-        return packages
-
-    @staticmethod
-    def __get_locked_package(
-        _dependency, packages_by_name
-    ):  # type: (Dependency, Dict[str, List[Package]]) -> Optional[Package]
-        """
-        Internal helper to identify corresponding locked package using dependency
-        version constraints.
-        """
-        for _package in packages_by_name.get(_dependency.name, []):
-            if _dependency.constraint.allows(_package.version):
-                return _package
-        return None
-
-    @classmethod
-    def __walk_dependency_level(
-        cls,
-        dependencies,
-        level,
-        pinned_versions,
-        packages_by_name,
-        project_level_dependencies,
-        nested_dependencies,
-    ):  # type: (List[Dependency], int,  bool, Dict[str, List[Package]], Set[str], Dict[Tuple[str, str], Dependency]) -> Dict[Tuple[str, str], Dependency]
-        if not dependencies:
-            return nested_dependencies
-
-        next_level_dependencies = []
-
-        for requirement in dependencies:
-            key = (requirement.name, requirement.pretty_constraint)
-            locked_package = cls.__get_locked_package(requirement, packages_by_name)
-
-            if locked_package:
-                # create dependency from locked package to retain dependency metadata
-                # if this is not done, we can end-up with incorrect nested dependencies
-                marker = requirement.marker
-                requirement = locked_package.to_dependency()
-                requirement.marker = requirement.marker.intersect(marker)
-
-                key = (requirement.name, requirement.pretty_constraint)
-
-                if pinned_versions:
-                    requirement.set_constraint(
-                        locked_package.to_dependency().constraint
-                    )
-
-                for require in locked_package.requires:
-                    if require.marker.is_empty():
-                        require.marker = requirement.marker
-                    else:
-                        require.marker = require.marker.intersect(requirement.marker)
-
-                    require.marker = require.marker.intersect(locked_package.marker)
-
-                    if key not in nested_dependencies:
-                        next_level_dependencies.append(require)
-
-            if requirement.name in project_level_dependencies and level == 0:
-                # project level dependencies take precedence
-                continue
-
-            if not locked_package:
-                # we make a copy to avoid any side-effects
-                requirement = deepcopy(requirement)
-
-            if key not in nested_dependencies:
-                nested_dependencies[key] = requirement
-            else:
-                nested_dependencies[key].marker = nested_dependencies[
-                    key
-                ].marker.intersect(requirement.marker)
-
-        return cls.__walk_dependency_level(
-            dependencies=next_level_dependencies,
-            level=level + 1,
-            pinned_versions=pinned_versions,
-            packages_by_name=packages_by_name,
-            project_level_dependencies=project_level_dependencies,
-            nested_dependencies=nested_dependencies,
-        )
-
-    @classmethod
-    def get_project_dependencies(
-        cls, project_requires, locked_packages, pinned_versions=False, with_nested=False
-    ):  # type: (List[Dependency], List[Package], bool, bool) -> Iterable[Dependency]
-        # group packages entries by name, this is required because requirement might use different constraints
-        packages_by_name = {}
-        for pkg in locked_packages:
-            if pkg.name not in packages_by_name:
-                packages_by_name[pkg.name] = []
-            packages_by_name[pkg.name].append(pkg)
-
-        project_level_dependencies = set()
-        dependencies = []
-
-        for dependency in project_requires:
-            dependency = deepcopy(dependency)
-            locked_package = cls.__get_locked_package(dependency, packages_by_name)
-            if locked_package:
-                locked_dependency = locked_package.to_dependency()
-                locked_dependency.marker = dependency.marker.intersect(
-                    locked_package.marker
-                )
-
-                if not pinned_versions:
-                    locked_dependency.set_constraint(dependency.constraint)
-
-                dependency = locked_dependency
-
-            project_level_dependencies.add(dependency.name)
-            dependencies.append(dependency)
-
-        if not with_nested:
-            # return only with project level dependencies
-            return dependencies
-
-        nested_dependencies = cls.__walk_dependency_level(
-            dependencies=dependencies,
-            level=0,
-            pinned_versions=pinned_versions,
-            packages_by_name=packages_by_name,
-            project_level_dependencies=project_level_dependencies,
-            nested_dependencies=dict(),
-        )
-
-        # Merge same dependencies using marker union
-        for requirement in dependencies:
-            key = (requirement.name, requirement.pretty_constraint)
-            if key not in nested_dependencies:
-                nested_dependencies[key] = requirement
-            else:
-                nested_dependencies[key].marker = nested_dependencies[key].marker.union(
-                    requirement.marker
-                )
-
-        return sorted(nested_dependencies.values(), key=lambda x: x.name.lower())
+            repository.add_package(package)
 
-    def get_project_dependency_packages(
-        self, project_requires, dev=False, extras=None
-    ):  # type: (List[Dependency], bool, Optional[Union[bool, Sequence[str]]]) -> Iterator[DependencyPackage]
-        repository = self.locked_repository(with_dev_reqs=dev)
+        return repository
 
-        # Build a set of all packages required by our selected extras
-        extra_package_names = (
-            None if (isinstance(extras, bool) and extras is True) else ()
-        )
-
-        if extra_package_names is not None:
-            extra_package_names = set(
-                get_extra_package_names(
-                    repository.packages, self.lock_data.get("extras", {}), extras or (),
-                )
-            )
-
-        # If a package is optional and we haven't opted in to it, do not select
-        selected = []
-        for dependency in project_requires:
-            try:
-                package = repository.find_packages(dependency=dependency)[0]
-            except IndexError:
-                continue
-
-            if extra_package_names is not None and (
-                package.optional and package.name not in extra_package_names
-            ):
-                # a package is locked as optional, but is not activated via extras
-                continue
-
-            selected.append(dependency)
-
-        for dependency in self.get_project_dependencies(
-            project_requires=selected,
-            locked_packages=repository.packages,
-            with_nested=True,
-        ):
-            try:
-                package = repository.find_packages(dependency=dependency)[0]
-            except IndexError:
-                continue
-
-            for extra in dependency.extras:
-                package.requires_extras.append(extra)
-
-            yield DependencyPackage(dependency=dependency, package=package)
-
-    def set_lock_data(self, root, packages):  # type: (...) -> bool
-        files = table()
-        packages = self._lock_packages(packages)
+    def set_lock_data(self, root: Package, packages: list[Package]) -> bool:
+        package_specs = self._lock_packages(packages)
         # Retrieving hashes
-        for package in packages:
-            if package["name"] not in files:
-                files[package["name"]] = []
+        for package in package_specs:
+            files = array()
 
             for f in package["files"]:
                 file_metadata = inline_table()
                 for k, v in sorted(f.items()):
                     file_metadata[k] = v
 
-                files[package["name"]].append(file_metadata)
-
-            if files[package["name"]]:
-                files[package["name"]] = item(files[package["name"]]).multiline(True)
+                files.append(file_metadata)
 
-            del package["files"]
+            package["files"] = files.multiline(True)
 
         lock = document()
-        lock["package"] = packages
+        lock.add(comment(GENERATED_COMMENT))
+        lock["package"] = package_specs
 
         if root.extras:
             lock["extras"] = {
@@ -413,32 +248,32 @@ def set_lock_data(self, root, packages):  # type: (...) -> bool
                 for extra, deps in sorted(root.extras.items())
             }
 
-        lock["metadata"] = OrderedDict(
-            [
-                ("lock-version", self._VERSION),
-                ("python-versions", root.python_versions),
-                ("content-hash", self._content_hash),
-                ("files", files),
-            ]
-        )
+        lock["metadata"] = {
+            "lock-version": self._VERSION,
+            "python-versions": root.python_versions,
+            "content-hash": self._content_hash,
+        }
 
-        if not self.is_locked() or lock != self.lock_data:
+        do_write = True
+        if self.is_locked():
+            try:
+                lock_data = self.lock_data
+            except RuntimeError:
+                # incompatible, invalid or no lock file
+                pass
+            else:
+                do_write = lock != lock_data
+        if do_write:
             self._write_lock_data(lock)
+        return do_write
 
-            return True
-
-        return False
-
-    def _write_lock_data(self, data):
-        self.lock.write(data)
-
-        # Checking lock file data consistency
-        if data != self.lock.read():
-            raise RuntimeError("Inconsistent lock file data.")
+    def _write_lock_data(self, data: TOMLDocument) -> None:
+        lockfile = TOMLFile(self.lock)
+        lockfile.write(data)
 
         self._lock_data = None
 
-    def _get_content_hash(self):  # type: () -> str
+    def _get_content_hash(self) -> str:
         """
         Returns the sha256 hash of the sorted content of the pyproject file.
         """
@@ -446,36 +281,36 @@ def _get_content_hash(self):  # type: () -> str
 
         relevant_content = {}
         for key in self._relevant_keys:
-            relevant_content[key] = content.get(key)
+            data = content.get(key)
+
+            if data is None and key not in self._legacy_keys:
+                continue
 
-        content_hash = sha256(
-            json.dumps(relevant_content, sort_keys=True).encode()
-        ).hexdigest()
+            relevant_content[key] = data
 
-        return content_hash
+        return sha256(json.dumps(relevant_content, sort_keys=True).encode()).hexdigest()
 
-    def _get_lock_data(self):  # type: () -> dict
-        if not self._lock.exists():
+    def _get_lock_data(self) -> dict[str, Any]:
+        if not self.lock.exists():
             raise RuntimeError("No lockfile found. Unable to read locked packages")
 
-        try:
-            lock_data = self._lock.read()
-        except TOMLKitError as e:
-            raise RuntimeError("Unable to read the lock file ({}).".format(e))
+        with self.lock.open("rb") as f:
+            try:
+                lock_data = tomllib.load(f)
+            except tomllib.TOMLDecodeError as e:
+                raise RuntimeError(f"Unable to read the lock file ({e}).")
 
-        lock_version = Version.parse(lock_data["metadata"].get("lock-version", "1.0"))
+        metadata = lock_data["metadata"]
+        lock_version = Version.parse(metadata.get("lock-version", "1.0"))
         current_version = Version.parse(self._VERSION)
-        # We expect the locker to be able to read lock files
-        # from the same semantic versioning range
-        accepted_versions = parse_constraint(
-            "^{}".format(Version(current_version.major, 0))
-        )
+        accepted_versions = parse_constraint(self._READ_VERSION_RANGE)
         lock_version_allowed = accepted_versions.allows(lock_version)
         if lock_version_allowed and current_version < lock_version:
             logger.warning(
-                "The lock file might not be compatible with the current version of Poetry.\n"
-                "Upgrade Poetry to ensure the lock file is read properly or, alternatively, "
-                "regenerate the lock file with the `poetry lock` command."
+                "The lock file might not be compatible with the current version of"
+                " Poetry.\nUpgrade Poetry to ensure the lock file is read properly or,"
+                " alternatively, regenerate the lock file with the `poetry lock`"
+                " command."
             )
         elif not lock_version_allowed:
             raise RuntimeError(
@@ -486,34 +321,55 @@ def _get_lock_data(self):  # type: () -> dict
 
         return lock_data
 
-    def _lock_packages(
-        self, packages
-    ):  # type: (List['poetry.packages.Package']) -> list
+    def _lock_packages(self, packages: list[Package]) -> list[dict[str, Any]]:
         locked = []
 
-        for package in sorted(packages, key=lambda x: x.name):
+        for package in sorted(
+            packages,
+            key=lambda x: (
+                x.name,
+                x.version,
+                x.source_type or "",
+                x.source_url or "",
+                x.source_subdirectory or "",
+                x.source_reference or "",
+                x.source_resolved_reference or "",
+            ),
+        ):
             spec = self._dump_package(package)
 
             locked.append(spec)
 
         return locked
 
-    def _dump_package(self, package):  # type: (Package) -> dict
-        dependencies = OrderedDict()
-        for dependency in sorted(package.requires, key=lambda d: d.name):
+    def _dump_package(self, package: Package) -> dict[str, Any]:
+        dependencies: dict[str, list[Any]] = {}
+        for dependency in sorted(
+            package.requires,
+            key=lambda d: d.name,
+        ):
             if dependency.pretty_name not in dependencies:
                 dependencies[dependency.pretty_name] = []
 
             constraint = inline_table()
 
-            if dependency.is_directory() or dependency.is_file():
+            if dependency.is_directory():
+                dependency = cast("DirectoryDependency", dependency)
                 constraint["path"] = dependency.path.as_posix()
 
-                if dependency.is_directory() and dependency.develop:
+                if dependency.develop:
                     constraint["develop"] = True
+
+            elif dependency.is_file():
+                dependency = cast("FileDependency", dependency)
+                constraint["path"] = dependency.path.as_posix()
+
             elif dependency.is_url():
+                dependency = cast("URLDependency", dependency)
                 constraint["url"] = dependency.url
+
             elif dependency.is_vcs():
+                dependency = cast("VCSDependency", dependency)
                 constraint[dependency.vcs] = dependency.source
 
                 if dependency.branch:
@@ -538,26 +394,27 @@ def _dump_package(self, package):  # type: (Package) -> dict
 
         # All the constraints should have the same type,
         # but we want to simplify them if it's possible
-        for dependency, constraints in tuple(dependencies.items()):
+        for dependency_name, constraints in dependencies.items():
             if all(
                 len(constraint) == 1 and "version" in constraint
                 for constraint in constraints
             ):
-                dependencies[dependency] = [
+                dependencies[dependency_name] = [
                     constraint["version"] for constraint in constraints
                 ]
 
-        data = OrderedDict(
-            [
-                ("name", package.pretty_name),
-                ("version", package.pretty_version),
-                ("description", package.description or ""),
-                ("category", package.category),
-                ("optional", package.optional),
-                ("python-versions", package.python_versions),
-                ("files", sorted(package.files, key=lambda x: x["file"])),
-            ]
-        )
+        data: dict[str, Any] = {
+            "name": package.pretty_name,
+            "version": package.pretty_version,
+            "description": package.description or "",
+            "category": package.category,
+            "optional": package.optional,
+            "python-versions": package.python_versions,
+            "files": sorted(
+                package.files,
+                key=lambda x: x["file"],  # type: ignore[no-any-return]
+            ),
+        }
 
         if dependencies:
             data["dependencies"] = table()
@@ -570,14 +427,9 @@ def _dump_package(self, package):  # type: (Package) -> dict
                         data["dependencies"][k].append(constraint)
 
         if package.extras:
-            extras = OrderedDict()
+            extras = {}
             for name, deps in sorted(package.extras.items()):
-                # TODO: This should use dep.to_pep_508() once this is fixed
-                # https://github.com/python-poetry/poetry-core/pull/102
-                extras[name] = sorted(
-                    dep.base_pep_508_name if not dep.constraint.is_any() else dep.name
-                    for dep in deps
-                )
+                extras[name] = sorted(dep.base_pep_508_name for dep in deps)
 
             data["extras"] = extras
 
@@ -587,11 +439,12 @@ def _dump_package(self, package):  # type: (Package) -> dict
                 # The lock file should only store paths relative to the root project
                 url = Path(
                     os.path.relpath(
-                        Path(url).as_posix(), self._lock.path.parent.as_posix()
+                        Path(url).resolve(),
+                        Path(self.lock.parent).resolve(),
                     )
                 ).as_posix()
 
-            data["source"] = OrderedDict()
+            data["source"] = {}
 
             if package.source_type:
                 data["source"]["type"] = package.source_type
@@ -604,12 +457,10 @@ def _dump_package(self, package):  # type: (Package) -> dict
             if package.source_resolved_reference:
                 data["source"]["resolved_reference"] = package.source_resolved_reference
 
+            if package.source_subdirectory:
+                data["source"]["subdirectory"] = package.source_subdirectory
+
             if package.source_type in ["directory", "git"]:
                 data["develop"] = package.develop
 
         return data
-
-
-class NullLocker(Locker):
-    def set_lock_data(self, root, packages):  # type: (Package, List[Package]) -> None
-        pass
diff --git a/conda_lock/_vendor/poetry/packages/package_collection.py b/conda_lock/_vendor/poetry/packages/package_collection.py
index e10ea635b..fe77136a9 100644
--- a/conda_lock/_vendor/poetry/packages/package_collection.py
+++ b/conda_lock/_vendor/poetry/packages/package_collection.py
@@ -1,22 +1,35 @@
-from .dependency_package import DependencyPackage
+from __future__ import annotations
 
+from typing import TYPE_CHECKING
+from typing import List
 
-class PackageCollection(list):
-    def __init__(self, dependency, packages=None):
-        self._dependency = dependency
+from conda_lock._vendor.poetry.packages.dependency_package import DependencyPackage
+
+
+if TYPE_CHECKING:
+    from collections.abc import Iterable
 
-        if packages is None:
-            packages = []
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+
+class PackageCollection(List[DependencyPackage]):
+    def __init__(
+        self,
+        dependency: Dependency,
+        packages: Iterable[Package | DependencyPackage] = (),
+    ) -> None:
+        self._dependency = dependency
 
-        super(PackageCollection, self).__init__()
+        super().__init__()
 
         for package in packages:
             self.append(package)
 
-    def append(self, package):
+    def append(self, package: Package | DependencyPackage) -> None:
         if isinstance(package, DependencyPackage):
             package = package.package
 
         package = DependencyPackage(self._dependency, package)
 
-        return super(PackageCollection, self).append(package)
+        return super().append(package)
diff --git a/conda_lock/_vendor/poetry/plugins/__init__.py b/conda_lock/_vendor/poetry/plugins/__init__.py
new file mode 100644
index 000000000..038a620a5
--- /dev/null
+++ b/conda_lock/_vendor/poetry/plugins/__init__.py
@@ -0,0 +1,7 @@
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.plugins.application_plugin import ApplicationPlugin
+from conda_lock._vendor.poetry.plugins.plugin import Plugin
+
+
+__all__ = ["ApplicationPlugin", "Plugin"]
diff --git a/conda_lock/_vendor/poetry/plugins/application_plugin.py b/conda_lock/_vendor/poetry/plugins/application_plugin.py
new file mode 100644
index 000000000..6f2b284d4
--- /dev/null
+++ b/conda_lock/_vendor/poetry/plugins/application_plugin.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.plugins.base_plugin import BasePlugin
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.console.application import Application
+    from conda_lock._vendor.poetry.console.commands.command import Command
+
+
+class ApplicationPlugin(BasePlugin):
+    """
+    Base class for application plugins.
+    """
+
+    group = "poetry.application.plugin"
+
+    @property
+    def commands(self) -> list[type[Command]]:
+        return []
+
+    def activate(self, application: Application) -> None:
+        for command in self.commands:
+            assert command.name is not None
+            application.command_loader.register_factory(command.name, command)
diff --git a/conda_lock/_vendor/poetry/plugins/base_plugin.py b/conda_lock/_vendor/poetry/plugins/base_plugin.py
new file mode 100644
index 000000000..071460607
--- /dev/null
+++ b/conda_lock/_vendor/poetry/plugins/base_plugin.py
@@ -0,0 +1,21 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+
+
+class BasePlugin:
+    """
+    Base class for all plugin types
+
+    The `activate()` method must be implemented and receives the Poetry instance.
+    """
+
+    PLUGIN_API_VERSION = "1.0.0"
+
+    @property
+    @abstractmethod
+    def group(self) -> str:
+        """
+        Name of entrypoint group the plugin belongs to.
+        """
+        raise NotImplementedError()
diff --git a/conda_lock/_vendor/poetry/plugins/plugin.py b/conda_lock/_vendor/poetry/plugins/plugin.py
new file mode 100644
index 000000000..04b441dc2
--- /dev/null
+++ b/conda_lock/_vendor/poetry/plugins/plugin.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.plugins.base_plugin import BasePlugin
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.cleo.io.io import IO
+
+    from conda_lock._vendor.poetry.poetry import Poetry
+
+
+class Plugin(BasePlugin):
+    """
+    Generic plugin not related to the console application.
+    """
+
+    group = "poetry.plugin"
+
+    @abstractmethod
+    def activate(self, poetry: Poetry, io: IO) -> None:
+        raise NotImplementedError()
diff --git a/conda_lock/_vendor/poetry/plugins/plugin_manager.py b/conda_lock/_vendor/poetry/plugins/plugin_manager.py
new file mode 100644
index 000000000..17b197bd6
--- /dev/null
+++ b/conda_lock/_vendor/poetry/plugins/plugin_manager.py
@@ -0,0 +1,83 @@
+from __future__ import annotations
+
+import logging
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.plugins.application_plugin import ApplicationPlugin
+from conda_lock._vendor.poetry.plugins.plugin import Plugin
+from conda_lock._vendor.poetry.utils._compat import metadata
+
+
+if TYPE_CHECKING:
+    from typing import Any
+
+    from conda_lock._vendor.poetry.utils.env import Env
+
+
+logger = logging.getLogger(__name__)
+
+
+class PluginManager:
+    """
+    This class registers and activates plugins.
+    """
+
+    def __init__(self, group: str, disable_plugins: bool = False) -> None:
+        self._group = group
+        self._disable_plugins = disable_plugins
+        self._plugins: list[Plugin] = []
+
+    def load_plugins(self, env: Env | None = None) -> None:
+        if self._disable_plugins:
+            return
+
+        plugin_entrypoints = self.get_plugin_entry_points(env=env)
+
+        for ep in plugin_entrypoints:
+            self._load_plugin_entry_point(ep)
+
+    @staticmethod
+    def _is_plugin_candidate(ep: metadata.EntryPoint, env: Env | None = None) -> bool:
+        """
+        Helper method to check if given entry point is a valid as a plugin candidate.
+        When an environment is specified, the entry point's associated distribution
+        should be installed, and discoverable in the given environment.
+        """
+        return env is None or (
+            ep.dist is not None
+            and env.site_packages.find_distribution(ep.dist.name) is not None
+        )
+
+    def get_plugin_entry_points(
+        self, env: Env | None = None
+    ) -> list[metadata.EntryPoint]:
+        return [
+            ep
+            for ep in metadata.entry_points(group=self._group)
+            if self._is_plugin_candidate(ep, env)
+        ]
+
+    def add_plugin(self, plugin: Plugin) -> None:
+        if not isinstance(plugin, (Plugin, ApplicationPlugin)):
+            raise ValueError(
+                "The Poetry plugin must be an instance of Plugin or ApplicationPlugin"
+            )
+
+        self._plugins.append(plugin)
+
+    def activate(self, *args: Any, **kwargs: Any) -> None:
+        for plugin in self._plugins:
+            plugin.activate(*args, **kwargs)
+
+    def _load_plugin_entry_point(self, ep: metadata.EntryPoint) -> None:
+        logger.debug("Loading the %s plugin", ep.name)  # type: ignore[attr-defined]
+
+        plugin = ep.load()  # type: ignore[no-untyped-call]
+
+        if not issubclass(plugin, (Plugin, ApplicationPlugin)):
+            raise ValueError(
+                "The Poetry plugin must be an instance of Plugin or ApplicationPlugin"
+            )
+
+        self.add_plugin(plugin())
diff --git a/conda_lock/_vendor/poetry/poetry.py b/conda_lock/_vendor/poetry/poetry.py
index b18eb86dc..125f1d55e 100644
--- a/conda_lock/_vendor/poetry/poetry.py
+++ b/conda_lock/_vendor/poetry/poetry.py
@@ -1,57 +1,85 @@
-from __future__ import absolute_import
-from __future__ import unicode_literals
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+from typing import Any
 
-from conda_lock._vendor.poetry.core.packages import ProjectPackage
 from conda_lock._vendor.poetry.core.poetry import Poetry as BasePoetry
 
-from .__version__ import __version__
-from .config.config import Config
-from .packages import Locker
-from .repositories.pool import Pool
-from .utils._compat import Path
+from conda_lock._vendor.poetry.__version__ import __version__
+from conda_lock._vendor.poetry.config.source import Source
 
 
-class Poetry(BasePoetry):
+if TYPE_CHECKING:
+    from pathlib import Path
+
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
+
+    from conda_lock._vendor.poetry.config.config import Config
+    from conda_lock._vendor.poetry.packages.locker import Locker
+    from conda_lock._vendor.poetry.plugins.plugin_manager import PluginManager
+    from conda_lock._vendor.poetry.repositories.repository_pool import RepositoryPool
 
+
+class Poetry(BasePoetry):
     VERSION = __version__
 
     def __init__(
         self,
-        file,  # type: Path
-        local_config,  # type: dict
-        package,  # type: ProjectPackage
-        locker,  # type: Locker
-        config,  # type: Config
-    ):
-        super(Poetry, self).__init__(file, local_config, package)
+        file: Path,
+        local_config: dict[str, Any],
+        package: ProjectPackage,
+        locker: Locker,
+        config: Config,
+        disable_cache: bool = False,
+    ) -> None:
+        from conda_lock._vendor.poetry.repositories.repository_pool import RepositoryPool
+
+        super().__init__(file, local_config, package)
 
         self._locker = locker
         self._config = config
-        self._pool = Pool()
+        self._pool = RepositoryPool()
+        self._plugin_manager: PluginManager | None = None
+        self._disable_cache = disable_cache
 
     @property
-    def locker(self):  # type: () -> Locker
+    def locker(self) -> Locker:
         return self._locker
 
     @property
-    def pool(self):  # type: () -> Pool
+    def pool(self) -> RepositoryPool:
         return self._pool
 
     @property
-    def config(self):  # type: () -> Config
+    def config(self) -> Config:
         return self._config
 
-    def set_locker(self, locker):  # type: (Locker) -> Poetry
+    @property
+    def disable_cache(self) -> bool:
+        return self._disable_cache
+
+    def set_locker(self, locker: Locker) -> Poetry:
         self._locker = locker
 
         return self
 
-    def set_pool(self, pool):  # type: (Pool) -> Poetry
+    def set_pool(self, pool: RepositoryPool) -> Poetry:
         self._pool = pool
 
         return self
 
-    def set_config(self, config):  # type: (Config) -> Poetry
+    def set_config(self, config: Config) -> Poetry:
         self._config = config
 
         return self
+
+    def set_plugin_manager(self, plugin_manager: PluginManager) -> Poetry:
+        self._plugin_manager = plugin_manager
+
+        return self
+
+    def get_sources(self) -> list[Source]:
+        return [
+            Source(**source)
+            for source in self.pyproject.poetry_config.get("source", [])
+        ]
diff --git a/conda_lock/_vendor/poetry/puzzle/__init__.py b/conda_lock/_vendor/poetry/puzzle/__init__.py
index 70089f30c..d28c36b76 100644
--- a/conda_lock/_vendor/poetry/puzzle/__init__.py
+++ b/conda_lock/_vendor/poetry/puzzle/__init__.py
@@ -1 +1,6 @@
-from .solver import Solver
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.puzzle.solver import Solver
+
+
+__all__ = ["Solver"]
diff --git a/conda_lock/_vendor/poetry/puzzle/exceptions.py b/conda_lock/_vendor/poetry/puzzle/exceptions.py
index e2e0b0dcc..207a8529d 100644
--- a/conda_lock/_vendor/poetry/puzzle/exceptions.py
+++ b/conda_lock/_vendor/poetry/puzzle/exceptions.py
@@ -1,18 +1,32 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+
+    from conda_lock._vendor.poetry.mixology.failure import SolveFailure
+    from conda_lock._vendor.poetry.packages import DependencyPackage
+
+
 class SolverProblemError(Exception):
-    def __init__(self, error):
+    def __init__(self, error: SolveFailure) -> None:
         self._error = error
 
-        super(SolverProblemError, self).__init__(str(error))
+        super().__init__(str(error))
 
     @property
-    def error(self):
+    def error(self) -> SolveFailure:
         return self._error
 
 
 class OverrideNeeded(Exception):
-    def __init__(self, *overrides):
+    def __init__(
+        self, *overrides: dict[DependencyPackage, dict[str, Dependency]]
+    ) -> None:
         self._overrides = overrides
 
     @property
-    def overrides(self):
+    def overrides(self) -> tuple[dict[DependencyPackage, dict[str, Dependency]], ...]:
         return self._overrides
diff --git a/conda_lock/_vendor/poetry/puzzle/provider.py b/conda_lock/_vendor/poetry/puzzle/provider.py
old mode 100755
new mode 100644
index b41de8d63..056fd37d1
--- a/conda_lock/_vendor/poetry/puzzle/provider.py
+++ b/conda_lock/_vendor/poetry/puzzle/provider.py
@@ -1,26 +1,27 @@
+from __future__ import annotations
+
+import functools
 import logging
 import os
 import re
+import tempfile
 import time
+import urllib.parse
 
+from collections import defaultdict
 from contextlib import contextmanager
-from tempfile import mkdtemp
-from typing import Any
-from typing import List
-from typing import Optional
-
-from clikit.ui.components import ProgressIndicator
-
-from conda_lock._vendor.poetry.core.packages import Dependency
-from conda_lock._vendor.poetry.core.packages import DirectoryDependency
-from conda_lock._vendor.poetry.core.packages import FileDependency
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.packages import URLDependency
-from conda_lock._vendor.poetry.core.packages import VCSDependency
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Collection
+from typing import cast
+
+from conda_lock._vendor.cleo.ui.progress_indicator import ProgressIndicator
+from conda_lock._vendor.poetry.core.constraints.version import EmptyConstraint
+from conda_lock._vendor.poetry.core.constraints.version import Version
 from conda_lock._vendor.poetry.core.packages.utils.utils import get_python_constraint_from_marker
-from conda_lock._vendor.poetry.core.semver.version import Version
-from conda_lock._vendor.poetry.core.vcs.git import Git
+from conda_lock._vendor.poetry.core.version.markers import AnyMarker
 from conda_lock._vendor.poetry.core.version.markers import MarkerUnion
+
 from conda_lock._vendor.poetry.inspection.info import PackageInfo
 from conda_lock._vendor.poetry.inspection.info import PackageInfoError
 from conda_lock._vendor.poetry.mixology.incompatibility import Incompatibility
@@ -30,317 +31,466 @@
 from conda_lock._vendor.poetry.packages import DependencyPackage
 from conda_lock._vendor.poetry.packages.package_collection import PackageCollection
 from conda_lock._vendor.poetry.puzzle.exceptions import OverrideNeeded
-from conda_lock._vendor.poetry.repositories import Pool
-from conda_lock._vendor.poetry.utils._compat import OrderedDict
-from conda_lock._vendor.poetry.utils._compat import Path
-from conda_lock._vendor.poetry.utils._compat import urlparse
-from conda_lock._vendor.poetry.utils.env import Env
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
 from conda_lock._vendor.poetry.utils.helpers import download_file
-from conda_lock._vendor.poetry.utils.helpers import safe_rmtree
-from conda_lock._vendor.poetry.utils.helpers import temporary_directory
+from conda_lock._vendor.poetry.utils.helpers import get_file_hash
+from conda_lock._vendor.poetry.vcs.git import Git
+
+
+if TYPE_CHECKING:
+    from collections.abc import Callable
+    from collections.abc import Iterable
+    from collections.abc import Iterator
+
+    from conda_lock._vendor.cleo.io.io import IO
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.directory_dependency import DirectoryDependency
+    from conda_lock._vendor.poetry.core.packages.file_dependency import FileDependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from conda_lock._vendor.poetry.core.packages.url_dependency import URLDependency
+    from conda_lock._vendor.poetry.core.packages.vcs_dependency import VCSDependency
+    from conda_lock._vendor.poetry.core.version.markers import BaseMarker
+
+    from conda_lock._vendor.poetry.repositories import RepositoryPool
+    from conda_lock._vendor.poetry.utils.env import Env
 
 
 logger = logging.getLogger(__name__)
 
 
+class IncompatibleConstraintsError(Exception):
+    """
+    Exception when there are duplicate dependencies with incompatible constraints.
+    """
+
+    def __init__(self, package: Package, *dependencies: Dependency) -> None:
+        constraints = "\n".join(dep.to_pep_508() for dep in dependencies)
+        super().__init__(
+            f"Incompatible constraints in requirements of {package}:\n{constraints}"
+        )
+
+
 class Indicator(ProgressIndicator):
-    def _formatter_elapsed(self):
+    CONTEXT: str | None = None
+
+    @staticmethod
+    @contextmanager
+    def context() -> Iterator[Callable[[str | None], None]]:
+        def _set_context(context: str | None) -> None:
+            Indicator.CONTEXT = context
+
+        yield _set_context
+
+        _set_context(None)
+
+    def _formatter_context(self) -> str:
+        if Indicator.CONTEXT is None:
+            return " "
+        else:
+            return f" {Indicator.CONTEXT} "
+
+    def _formatter_elapsed(self) -> str:
+        assert self._start_time is not None
         elapsed = time.time() - self._start_time
 
-        return "{:.1f}s".format(elapsed)
+        return f"{elapsed:.1f}s"
+
+
+@functools.lru_cache(maxsize=None)
+def _get_package_from_git(
+    url: str,
+    branch: str | None = None,
+    tag: str | None = None,
+    rev: str | None = None,
+    subdirectory: str | None = None,
+    source_root: Path | None = None,
+) -> Package:
+    source = Git.clone(
+        url=url,
+        source_root=source_root,
+        branch=branch,
+        tag=tag,
+        revision=rev,
+        clean=False,
+    )
+    revision = Git.get_revision(source)
+
+    path = Path(source.path)
+    if subdirectory:
+        path = path.joinpath(subdirectory)
+
+    package = Provider.get_package_from_directory(path)
+    package._source_type = "git"
+    package._source_url = url
+    package._source_reference = rev or tag or branch or "HEAD"
+    package._source_resolved_reference = revision
+    package._source_subdirectory = subdirectory
+
+    return package
 
 
 class Provider:
-
-    UNSAFE_PACKAGES = {"setuptools", "distribute", "pip", "wheel"}
+    UNSAFE_PACKAGES: set[str] = set()
 
     def __init__(
-        self, package, pool, io, env=None
-    ):  # type: (Package, Pool, Any, Optional[Env]) -> None
+        self,
+        package: Package,
+        pool: RepositoryPool,
+        io: IO,
+        *,
+        installed: list[Package] | None = None,
+        locked: list[Package] | None = None,
+    ) -> None:
         self._package = package
         self._pool = pool
         self._io = io
-        self._env = env
+        self._env: Env | None = None
         self._python_constraint = package.python_constraint
-        self._search_for = {}
-        self._is_debugging = self._io.is_debug() or self._io.is_very_verbose()
-        self._in_progress = False
-        self._overrides = {}
-        self._deferred_cache = {}
+        self._is_debugging: bool = self._io.is_debug() or self._io.is_very_verbose()
+        self._overrides: dict[DependencyPackage, dict[str, Dependency]] = {}
+        self._deferred_cache: dict[Dependency, Package] = {}
         self._load_deferred = True
+        self._source_root: Path | None = None
+        self._installed_packages = installed if installed is not None else []
+        self._direct_origin_packages: dict[str, Package] = {}
+        self._locked: dict[NormalizedName, list[DependencyPackage]] = defaultdict(list)
+        self._use_latest: Collection[NormalizedName] = []
+
+        for package in locked or []:
+            self._locked[package.name].append(
+                DependencyPackage(package.to_dependency(), package)
+            )
+        for dependency_packages in self._locked.values():
+            dependency_packages.sort(
+                key=lambda p: p.package.version,
+                reverse=True,
+            )
 
     @property
-    def pool(self):  # type: () -> Pool
+    def pool(self) -> RepositoryPool:
         return self._pool
 
-    def is_debugging(self):
+    @property
+    def use_latest(self) -> Collection[NormalizedName]:
+        return self._use_latest
+
+    def is_debugging(self) -> bool:
         return self._is_debugging
 
-    def set_overrides(self, overrides):
+    def set_overrides(
+        self, overrides: dict[DependencyPackage, dict[str, Dependency]]
+    ) -> None:
         self._overrides = overrides
 
-    def load_deferred(self, load_deferred):  # type: (bool) -> None
+    def load_deferred(self, load_deferred: bool) -> None:
         self._load_deferred = load_deferred
 
     @contextmanager
-    def use_environment(self, env):  # type: (Env) -> Provider
-        original_env = self._env
+    def use_source_root(self, source_root: Path) -> Iterator[Provider]:
+        original_source_root = self._source_root
+        self._source_root = source_root
+
+        try:
+            yield self
+        finally:
+            self._source_root = original_source_root
+
+    @contextmanager
+    def use_environment(self, env: Env) -> Iterator[Provider]:
         original_python_constraint = self._python_constraint
 
         self._env = env
         self._python_constraint = Version.parse(env.marker_env["python_full_version"])
 
-        yield self
+        try:
+            yield self
+        finally:
+            self._env = None
+            self._python_constraint = original_python_constraint
+
+    @contextmanager
+    def use_latest_for(self, names: Collection[NormalizedName]) -> Iterator[Provider]:
+        self._use_latest = names
+
+        try:
+            yield self
+        finally:
+            self._use_latest = []
 
-        self._env = original_env
-        self._python_constraint = original_python_constraint
+    @staticmethod
+    def validate_package_for_dependency(
+        dependency: Dependency, package: Package
+    ) -> None:
+        if dependency.name != package.name:
+            # For now, the dependency's name must match the actual package's name
+            raise RuntimeError(
+                f"The dependency name for {dependency.name} does not match the actual"
+                f" package's name: {package.name}"
+            )
 
-    def search_for(self, dependency):  # type: (Dependency) -> List[Package]
+    def search_for_installed_packages(
+        self,
+        dependency: Dependency,
+    ) -> list[Package]:
         """
-        Search for the specifications that match the given dependency.
+        Search for installed packages, when available, that satisfy the given
+        dependency.
 
-        The specifications in the returned list will be considered in reverse
-        order, so the latest version ought to be last.
+        This is useful when dealing with packages that are under development, not
+        published on package sources and/or only available via system installations.
         """
-        if dependency.is_root:
-            return PackageCollection(dependency, [self._package])
+        if not self._installed_packages:
+            return []
 
-        for constraint in self._search_for.keys():
-            if (
-                constraint.is_same_package_as(dependency)
-                and constraint.constraint.intersect(dependency.constraint)
-                == dependency.constraint
-            ):
-                packages = [
-                    p
-                    for p in self._search_for[constraint]
-                    if dependency.constraint.allows(p.version)
-                ]
-
-                packages.sort(
-                    key=lambda p: (
-                        not p.is_prerelease() and not dependency.allows_prereleases(),
-                        p.version,
-                    ),
-                    reverse=True,
-                )
+        logger.debug(
+            "Falling back to installed packages to discover metadata for %s",
+            dependency.complete_name,
+        )
+        packages = [
+            package
+            for package in self._installed_packages
+            if package.satisfies(dependency, ignore_source_type=True)
+        ]
+        logger.debug(
+            "Found %d compatible packages for %s",
+            len(packages),
+            dependency.complete_name,
+        )
+        return packages
 
-                return PackageCollection(dependency, packages)
+    def search_for_direct_origin_dependency(self, dependency: Dependency) -> Package:
+        package = self._deferred_cache.get(dependency)
+        if package is not None:
+            pass
+
+        elif dependency.is_vcs():
+            dependency = cast("VCSDependency", dependency)
+            package = self._search_for_vcs(dependency)
 
-        if dependency.is_vcs():
-            packages = self.search_for_vcs(dependency)
         elif dependency.is_file():
-            packages = self.search_for_file(dependency)
+            dependency = cast("FileDependency", dependency)
+            package = self._search_for_file(dependency)
+
         elif dependency.is_directory():
-            packages = self.search_for_directory(dependency)
+            dependency = cast("DirectoryDependency", dependency)
+            package = self._search_for_directory(dependency)
+
         elif dependency.is_url():
-            packages = self.search_for_url(dependency)
+            dependency = cast("URLDependency", dependency)
+            package = self._search_for_url(dependency)
+
         else:
-            packages = self._pool.find_packages(dependency)
+            raise RuntimeError(
+                f"{dependency}: unknown direct dependency type {dependency.source_type}"
+            )
 
-            packages.sort(
-                key=lambda p: (
-                    not p.is_prerelease() and not dependency.allows_prereleases(),
-                    p.version,
-                ),
-                reverse=True,
+        if dependency.is_vcs():
+            dependency._source_reference = package.source_reference
+            dependency._source_resolved_reference = package.source_resolved_reference
+            dependency._source_subdirectory = package.source_subdirectory
+
+        dependency._constraint = package.version
+        dependency._pretty_constraint = package.version.text
+
+        self._deferred_cache[dependency] = package
+
+        return package
+
+    def search_for(self, dependency: Dependency) -> list[DependencyPackage]:
+        """
+        Search for the specifications that match the given dependency.
+
+        The specifications in the returned list will be considered in reverse
+        order, so the latest version ought to be last.
+        """
+        if dependency.is_root:
+            return PackageCollection(dependency, [self._package])
+
+        if dependency.is_direct_origin():
+            package = self.search_for_direct_origin_dependency(dependency)
+            self._direct_origin_packages[dependency.name] = package
+            return PackageCollection(dependency, [package])
+
+        # If we've previously found a direct-origin package that meets this dependency,
+        # use it.
+        #
+        # We rely on the VersionSolver resolving direct-origin dependencies first.
+        direct_origin_package = self._direct_origin_packages.get(dependency.name)
+        if direct_origin_package is not None:
+            packages = (
+                [direct_origin_package]
+                if dependency.constraint.allows(direct_origin_package.version)
+                else []
             )
+            return PackageCollection(dependency, packages)
 
-        self._search_for[dependency] = packages
+        packages = self._pool.find_packages(dependency)
+
+        packages.sort(
+            key=lambda p: (
+                not p.yanked,
+                not p.is_prerelease() and not dependency.allows_prereleases(),
+                p.version,
+            ),
+            reverse=True,
+        )
+
+        if not packages:
+            packages = self.search_for_installed_packages(dependency)
 
         return PackageCollection(dependency, packages)
 
-    def search_for_vcs(self, dependency):  # type: (VCSDependency) -> List[Package]
+    def _search_for_vcs(self, dependency: VCSDependency) -> Package:
         """
         Search for the specifications that match the given VCS dependency.
 
         Basically, we clone the repository in a temporary directory
         and get the information we need by checking out the specified reference.
         """
-        if dependency in self._deferred_cache:
-            return [self._deferred_cache[dependency]]
-
         package = self.get_package_from_vcs(
             dependency.vcs,
             dependency.source,
             branch=dependency.branch,
             tag=dependency.tag,
             rev=dependency.rev,
-            name=dependency.name,
+            subdirectory=dependency.source_subdirectory,
+            source_root=self._source_root
+            or (self._env.path.joinpath("src") if self._env else None),
         )
-        package.develop = dependency.develop
 
-        dependency._constraint = package.version
-        dependency._pretty_constraint = package.version.text
+        self.validate_package_for_dependency(dependency=dependency, package=package)
 
-        self._deferred_cache[dependency] = package
+        package.develop = dependency.develop
 
-        return [package]
+        return package
 
-    @classmethod
+    @staticmethod
     def get_package_from_vcs(
-        cls, vcs, url, branch=None, tag=None, rev=None, name=None
-    ):  # type: (str, str, Optional[str], Optional[str]) -> Package
+        vcs: str,
+        url: str,
+        branch: str | None = None,
+        tag: str | None = None,
+        rev: str | None = None,
+        subdirectory: str | None = None,
+        source_root: Path | None = None,
+    ) -> Package:
         if vcs != "git":
-            raise ValueError("Unsupported VCS dependency {}".format(vcs))
-
-        tmp_dir = Path(
-            mkdtemp(prefix="pypoetry-git-{}".format(url.split("/")[-1].rstrip(".git")))
+            raise ValueError(f"Unsupported VCS dependency {vcs}")
+
+        return _get_package_from_git(
+            url=url,
+            branch=branch,
+            tag=tag,
+            rev=rev,
+            subdirectory=subdirectory,
+            source_root=source_root,
         )
 
-        try:
-            git = Git()
-            git.clone(url, tmp_dir)
-            reference = branch or tag or rev
-            if reference is not None:
-                git.checkout(reference, tmp_dir)
-            else:
-                reference = "HEAD"
-
-            revision = git.rev_parse(reference, tmp_dir).strip()
-
-            package = cls.get_package_from_directory(tmp_dir, name=name)
-            package._source_type = "git"
-            package._source_url = url
-            package._source_reference = reference
-            package._source_resolved_reference = revision
-        except Exception:
-            raise
-        finally:
-            safe_rmtree(str(tmp_dir))
-
-        return package
+    def _search_for_file(self, dependency: FileDependency) -> Package:
+        package = self.get_package_from_file(dependency.full_path)
 
-    def search_for_file(self, dependency):  # type: (FileDependency) -> List[Package]
-        if dependency in self._deferred_cache:
-            dependency, _package = self._deferred_cache[dependency]
-
-            package = _package.clone()
-        else:
-            package = self.get_package_from_file(dependency.full_path)
-
-            dependency._constraint = package.version
-            dependency._pretty_constraint = package.version.text
-
-            self._deferred_cache[dependency] = (dependency, package)
-
-        if dependency.name != package.name:
-            # For now, the dependency's name must match the actual package's name
-            raise RuntimeError(
-                "The dependency name for {} does not match the actual package's name: {}".format(
-                    dependency.name, package.name
-                )
-            )
+        self.validate_package_for_dependency(dependency=dependency, package=package)
 
         if dependency.base is not None:
             package.root_dir = dependency.base
 
         package.files = [
-            {"file": dependency.path.name, "hash": "sha256:" + dependency.hash()}
+            {
+                "file": dependency.path.name,
+                "hash": "sha256:" + get_file_hash(dependency.full_path),
+            }
         ]
 
-        return [package]
+        return package
 
     @classmethod
-    def get_package_from_file(cls, file_path):  # type: (Path) -> Package
+    def get_package_from_file(cls, file_path: Path) -> Package:
         try:
             package = PackageInfo.from_path(path=file_path).to_package(
                 root_dir=file_path
             )
         except PackageInfoError:
             raise RuntimeError(
-                "Unable to determine package info from path: {}".format(file_path)
+                f"Unable to determine package info from path: {file_path}"
             )
 
         return package
 
-    def search_for_directory(
-        self, dependency
-    ):  # type: (DirectoryDependency) -> List[Package]
-        if dependency in self._deferred_cache:
-            dependency, _package = self._deferred_cache[dependency]
-
-            package = _package.clone()
-        else:
-            package = self.get_package_from_directory(
-                dependency.full_path, name=dependency.name
-            )
-
-            dependency._constraint = package.version
-            dependency._pretty_constraint = package.version.text
+    def _search_for_directory(self, dependency: DirectoryDependency) -> Package:
+        package = self.get_package_from_directory(dependency.full_path)
 
-            self._deferred_cache[dependency] = (dependency, package)
+        self.validate_package_for_dependency(dependency=dependency, package=package)
 
         package.develop = dependency.develop
 
         if dependency.base is not None:
             package.root_dir = dependency.base
 
-        return [package]
-
-    @classmethod
-    def get_package_from_directory(
-        cls, directory, name=None
-    ):  # type: (Path, Optional[str]) -> Package
-        package = PackageInfo.from_directory(path=directory).to_package(
-            root_dir=directory
-        )
-
-        if name and name != package.name:
-            # For now, the dependency's name must match the actual package's name
-            raise RuntimeError(
-                "The dependency name for {} does not match the actual package's name: {}".format(
-                    name, package.name
-                )
-            )
-
         return package
 
-    def search_for_url(self, dependency):  # type: (URLDependency) -> List[Package]
-        if dependency in self._deferred_cache:
-            return [self._deferred_cache[dependency]]
+    @classmethod
+    def get_package_from_directory(cls, directory: Path) -> Package:
+        return PackageInfo.from_directory(path=directory).to_package(root_dir=directory)
 
+    def _search_for_url(self, dependency: URLDependency) -> Package:
         package = self.get_package_from_url(dependency.url)
 
-        if dependency.name != package.name:
-            # For now, the dependency's name must match the actual package's name
-            raise RuntimeError(
-                "The dependency name for {} does not match the actual package's name: {}".format(
-                    dependency.name, package.name
-                )
-            )
+        self.validate_package_for_dependency(dependency=dependency, package=package)
 
         for extra in dependency.extras:
             if extra in package.extras:
                 for dep in package.extras[extra]:
                     dep.activate()
 
-                package.requires += package.extras[extra]
-
-        dependency._constraint = package.version
-        dependency._pretty_constraint = package.version.text
-
-        self._deferred_cache[dependency] = package
+                for extra_dep in package.extras[extra]:
+                    package.add_dependency(extra_dep)
 
-        return [package]
+        return package
 
     @classmethod
-    def get_package_from_url(cls, url):  # type: (str) -> Package
-        with temporary_directory() as temp_dir:
-            temp_dir = Path(temp_dir)
-            file_name = os.path.basename(urlparse.urlparse(url).path)
-            download_file(url, str(temp_dir / file_name))
+    def get_package_from_url(cls, url: str) -> Package:
+        file_name = os.path.basename(urllib.parse.urlparse(url).path)
+        with tempfile.TemporaryDirectory() as temp_dir:
+            dest = Path(temp_dir) / file_name
+            download_file(url, dest)
+            package = cls.get_package_from_file(dest)
 
-            package = cls.get_package_from_file(temp_dir / file_name)
+            package.files = [
+                {"file": file_name, "hash": "sha256:" + get_file_hash(dest)}
+            ]
 
         package._source_type = "url"
         package._source_url = url
 
         return package
 
+    def _get_dependencies_with_overrides(
+        self, dependencies: list[Dependency], package: DependencyPackage
+    ) -> list[Dependency]:
+        overrides = self._overrides.get(package, {})
+        _dependencies = []
+        overridden = []
+        for dep in dependencies:
+            if dep.name in overrides:
+                if dep.name in overridden:
+                    continue
+
+                # empty constraint is used in overrides to mark that the package has
+                # already been handled and is not required for the attached markers
+                if not overrides[dep.name].constraint.is_empty():
+                    _dependencies.append(overrides[dep.name])
+                overridden.append(dep.name)
+
+                continue
+
+            _dependencies.append(dep)
+        return _dependencies
+
     def incompatibilities_for(
-        self, package
-    ):  # type: (DependencyPackage) -> List[Incompatibility]
+        self, dependency_package: DependencyPackage
+    ) -> list[Incompatibility]:
         """
         Returns incompatibilities that encapsulate a given package's dependencies,
         or that it can't be safely selected.
@@ -350,6 +500,7 @@ def incompatibilities_for(
         won't return incompatibilities that have already been returned by a
         previous call to _incompatibilities_for().
         """
+        package = dependency_package.package
         if package.is_root():
             dependencies = package.all_requires
         else:
@@ -357,7 +508,7 @@ def incompatibilities_for(
 
             if not package.python_constraint.allows_all(self._python_constraint):
                 transitive_python_constraint = get_python_constraint_from_marker(
-                    package.dependency.transitive_marker
+                    dependency_package.dependency.transitive_marker
                 )
                 intersection = package.python_constraint.intersect(
                     transitive_python_constraint
@@ -370,7 +521,7 @@ def incompatibilities_for(
                 if (
                     transitive_python_constraint.is_any()
                     or self._python_constraint.intersect(
-                        package.dependency.python_constraint
+                        dependency_package.dependency.python_constraint
                     ).is_empty()
                     or intersection.is_empty()
                     or not difference.is_empty()
@@ -391,21 +542,9 @@ def incompatibilities_for(
             and self._python_constraint.allows_any(dep.python_constraint)
             and (not self._env or dep.marker.validate(self._env.marker_env))
         ]
-
-        overrides = self._overrides.get(package, {})
-        dependencies = []
-        overridden = []
-        for dep in _dependencies:
-            if dep.name in overrides:
-                if dep.name in overridden:
-                    continue
-
-                dependencies.append(overrides[dep.name])
-                overridden.append(dep.name)
-
-                continue
-
-            dependencies.append(dep)
+        dependencies = self._get_dependencies_with_overrides(
+            _dependencies, dependency_package
+        )
 
         return [
             Incompatibility(
@@ -416,42 +555,41 @@ def incompatibilities_for(
         ]
 
     def complete_package(
-        self, package
-    ):  # type: (DependencyPackage) -> DependencyPackage
+        self, dependency_package: DependencyPackage
+    ) -> DependencyPackage:
+        package = dependency_package.package
+        dependency = dependency_package.dependency
 
         if package.is_root():
-            package = package.clone()
+            dependency_package = dependency_package.clone()
+            package = dependency_package.package
+            dependency = dependency_package.dependency
             requires = package.all_requires
-        elif not package.is_root() and package.source_type not in {
-            "directory",
-            "file",
-            "url",
-            "git",
-        }:
-            package = DependencyPackage(
-                package.dependency,
-                self._pool.package(
-                    package.name,
-                    package.version.text,
-                    extras=list(package.dependency.extras),
-                    repository=package.dependency.source_name,
-                ),
-            )
+        elif package.is_direct_origin():
             requires = package.requires
         else:
-            requires = package.requires
+            try:
+                dependency_package = DependencyPackage(
+                    dependency,
+                    self._pool.package(
+                        package.pretty_name,
+                        package.version,
+                        extras=list(dependency.extras),
+                        repository_name=dependency.source_name,
+                    ),
+                )
+            except PackageNotFound as e:
+                try:
+                    dependency_package = next(
+                        DependencyPackage(dependency, pkg)
+                        for pkg in self.search_for_installed_packages(dependency)
+                    )
+                except StopIteration:
+                    raise e from e
 
-        if self._load_deferred:
-            # Retrieving constraints for deferred dependencies
-            for r in requires:
-                if r.is_directory():
-                    self.search_for_directory(r)
-                elif r.is_file():
-                    self.search_for_file(r)
-                elif r.is_vcs():
-                    self.search_for_vcs(r)
-                elif r.is_url():
-                    self.search_for_url(r)
+            package = dependency_package.package
+            dependency = dependency_package.dependency
+            requires = package.requires
 
         optional_dependencies = []
         _dependencies = []
@@ -459,15 +597,27 @@ def complete_package(
         # If some extras/features were required, we need to
         # add a special dependency representing the base package
         # to the current package
-        if package.dependency.extras:
-            for extra in package.dependency.extras:
+        if dependency.extras:
+            for extra in dependency.extras:
                 if extra not in package.extras:
                     continue
 
                 optional_dependencies += [d.name for d in package.extras[extra]]
 
-            package = package.with_features(list(package.dependency.extras))
-            _dependencies.append(package.without_features().to_dependency())
+            dependency_package = dependency_package.with_features(
+                list(dependency.extras)
+            )
+            package = dependency_package.package
+            dependency = dependency_package.dependency
+            new_dependency = package.without_features().to_dependency()
+
+            # When adding dependency foo[extra] -> foo, preserve foo's source, if it's
+            # specified. This prevents us from trying to get foo from PyPI
+            # when user explicitly set repo for foo[extra].
+            if not new_dependency.source_name and dependency.source_name:
+                new_dependency.source_name = dependency.source_name
+
+            _dependencies.append(new_dependency)
 
         for dep in requires:
             if not self._python_constraint.allows_any(dep.python_constraint):
@@ -479,29 +629,32 @@ def complete_package(
             if self._env and not dep.marker.validate(self._env.marker_env):
                 continue
 
-            if not package.is_root():
-                if (dep.is_optional() and dep.name not in optional_dependencies) or (
+            if not package.is_root() and (
+                (dep.is_optional() and dep.name not in optional_dependencies)
+                or (
                     dep.in_extras
-                    and not set(dep.in_extras).intersection(package.dependency.extras)
-                ):
-                    continue
+                    and not set(dep.in_extras).intersection(dependency.extras)
+                )
+            ):
+                continue
 
             _dependencies.append(dep)
 
-        overrides = self._overrides.get(package, {})
-        dependencies = []
-        overridden = []
-        for dep in _dependencies:
-            if dep.name in overrides:
-                if dep.name in overridden:
-                    continue
-
-                dependencies.append(overrides[dep.name])
-                overridden.append(dep.name)
-
-                continue
+        if self._load_deferred:
+            # Retrieving constraints for deferred dependencies
+            for dep in _dependencies:
+                if dep.is_direct_origin():
+                    locked = self.get_locked(dep)
+                    # If lock file contains exactly the same URL and reference
+                    # (commit hash) of dependency as is requested,
+                    # do not analyze it again: nothing could have changed.
+                    if locked is not None and locked.package.is_same_package_as(dep):
+                        continue
+                    self.search_for_direct_origin_dependency(dep)
 
-            dependencies.append(dep)
+        dependencies = self._get_dependencies_with_overrides(
+            _dependencies, dependency_package
+        )
 
         # Searching for duplicate dependencies
         #
@@ -521,12 +674,9 @@ def complete_package(
         # An example of this is:
         #   - pypiwin32 (220); sys_platform == "win32" and python_version >= "3.6"
         #   - pypiwin32 (219); sys_platform == "win32" and python_version < "3.6"
-        duplicates = OrderedDict()
+        duplicates: dict[str, list[Dependency]] = defaultdict(list)
         for dep in dependencies:
-            if dep.name not in duplicates:
-                duplicates[dep.name] = []
-
-            duplicates[dep.name].append(dep)
+            duplicates[dep.complete_name].append(dep)
 
         dependencies = []
         for dep_name, deps in duplicates.items():
@@ -534,51 +684,43 @@ def complete_package(
                 dependencies.append(deps[0])
                 continue
 
-            self.debug("Duplicate dependencies for {}".format(dep_name))
-
-            # Regrouping by constraint
-            by_constraint = OrderedDict()
-            for dep in deps:
-                if dep.constraint not in by_constraint:
-                    by_constraint[dep.constraint] = []
-
-                by_constraint[dep.constraint].append(dep)
-
-            # We merge by constraint
-            for constraint, _deps in by_constraint.items():
-                new_markers = []
-                for dep in _deps:
-                    marker = dep.marker.without_extras()
-                    if marker.is_any():
-                        # No marker or only extras
-                        continue
-
-                    new_markers.append(marker)
-
-                if not new_markers:
-                    continue
-
-                dep = _deps[0]
-                dep.marker = dep.marker.union(MarkerUnion(*new_markers))
-                by_constraint[constraint] = [dep]
-
-                continue
-
-            if len(by_constraint) == 1:
-                self.debug(
-                    "Merging requirements for {}".format(str(deps[0]))
+            self.debug(f"Duplicate dependencies for {dep_name}")
+
+            # Group dependencies for merging.
+            # We must not merge dependencies from different sources!
+            dep_groups = self._group_by_source(deps)
+            deps = []
+            for group in dep_groups:
+                # In order to reduce the number of overrides we merge duplicate
+                # dependencies by constraint. For instance, if we have:
+                #   - foo (>=2.0) ; python_version >= "3.6" and python_version < "3.7"
+                #   - foo (>=2.0) ; python_version >= "3.7"
+                # we can avoid two overrides by merging them to:
+                #   - foo (>=2.0) ; python_version >= "3.6"
+                # However, if we want to merge dependencies by constraint we have to
+                # merge dependencies by markers first in order to avoid unnecessary
+                # solver failures. For instance, if we have:
+                #   - foo (>=2.0) ; python_version >= "3.6" and python_version < "3.7"
+                #   - foo (>=2.0) ; python_version >= "3.7"
+                #   - foo (<2.1) ; python_version >= "3.7"
+                # we must not merge the first two constraints but the last two:
+                #   - foo (>=2.0) ; python_version >= "3.6" and python_version < "3.7"
+                #   - foo (>=2.0,<2.1) ; python_version >= "3.7"
+                deps += self._merge_dependencies_by_constraint(
+                    self._merge_dependencies_by_marker(group)
                 )
-                dependencies.append(list(by_constraint.values())[0][0])
+            if len(deps) == 1:
+                self.debug(f"Merging requirements for {deps[0]!s}")
+                dependencies.append(deps[0])
                 continue
 
             # We leave dependencies as-is if they have the same
             # python/platform constraints.
             # That way the resolver will pickup the conflict
             # and display a proper error.
-            _deps = [value[0] for value in by_constraint.values()]
             seen = set()
-            for _dep in _deps:
-                pep_508_dep = _dep.to_pep_508(False)
+            for dep in deps:
+                pep_508_dep = dep.to_pep_508(False)
                 if ";" not in pep_508_dep:
                     _requirements = ""
                 else:
@@ -587,9 +729,9 @@ def complete_package(
                 if _requirements not in seen:
                     seen.add(_requirements)
 
-            if len(_deps) != len(seen):
-                for _dep in _deps:
-                    dependencies.append(_dep)
+            if len(deps) != len(seen):
+                for dep in deps:
+                    dependencies.append(dep)
 
                 continue
 
@@ -604,67 +746,50 @@ def complete_package(
             # with the following overrides:
             #   - {=2.0)>}
             #   - {}
-            markers = []
-            for constraint, _deps in by_constraint.items():
-                markers.append(_deps[0].marker)
 
-            _deps = [_dep[0] for _dep in by_constraint.values()]
-            self.debug(
-                "Different requirements found for {}.".format(
-                    ", ".join(
-                        "{} ({}) with markers {}".format(
-                            d.name,
-                            d.pretty_constraint,
-                            d.marker if not d.marker.is_any() else "*",
-                        )
-                        for d in _deps[:-1]
-                    )
-                    + " and "
-                    + "{} ({}) with markers {}".format(
-                        _deps[-1].name,
-                        _deps[-1].pretty_constraint,
-                        _deps[-1].marker if not _deps[-1].marker.is_any() else "*",
-                    )
+            def fmt_warning(d: Dependency) -> str:
+                dependency_marker = d.marker if not d.marker.is_any() else "*"
+                return (
+                    f"{d.name} ({d.pretty_constraint})"
+                    f" with markers {dependency_marker}"
                 )
-            )
 
-            # We need to check if one of the duplicate dependencies
-            # has no markers. If there is one, we need to change its
-            # environment markers to the inverse of the union of the
-            # other dependencies markers.
-            # For instance, if we have the following dependencies:
-            #   - ipython
-            #   - ipython (1.2.4) ; implementation_name == "pypy"
-            #
-            # the marker for `ipython` will become `implementation_name != "pypy"`.
-            any_markers_dependencies = [d for d in _deps if d.marker.is_any()]
-            other_markers_dependencies = [d for d in _deps if not d.marker.is_any()]
-
-            if any_markers_dependencies:
-                marker = other_markers_dependencies[0].marker
-                for other_dep in other_markers_dependencies[1:]:
-                    marker = marker.union(other_dep.marker)
+            warnings = ", ".join(fmt_warning(d) for d in deps[:-1])
+            warnings += f" and {fmt_warning(deps[-1])}"
+            self.debug(
+                f"Different requirements found for {warnings}."
+            )
 
-                for i, d in enumerate(_deps):
-                    if d.marker.is_any():
-                        _deps[i].marker = marker.invert()
+            deps = self._handle_any_marker_dependencies(package, deps)
 
             overrides = []
-            for _dep in _deps:
-                current_overrides = self._overrides.copy()
-                package_overrides = current_overrides.get(package, {}).copy()
-                package_overrides.update({_dep.name: _dep})
-                current_overrides.update({package: package_overrides})
-                overrides.append(current_overrides)
-
-            raise OverrideNeeded(*overrides)
+            overrides_marker_intersection: BaseMarker = AnyMarker()
+            for dep_overrides in self._overrides.values():
+                for dep in dep_overrides.values():
+                    overrides_marker_intersection = (
+                        overrides_marker_intersection.intersect(dep.marker)
+                    )
+            for dep in deps:
+                if not overrides_marker_intersection.intersect(dep.marker).is_empty():
+                    current_overrides = self._overrides.copy()
+                    package_overrides = current_overrides.get(
+                        dependency_package, {}
+                    ).copy()
+                    package_overrides.update({dep.name: dep})
+                    current_overrides.update({dependency_package: package_overrides})
+                    overrides.append(current_overrides)
+
+            if overrides:
+                raise OverrideNeeded(*overrides)
 
         # Modifying dependencies as needed
         clean_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 not dependency.transitive_marker.without_extras().is_any():
+                marker_intersection = (
+                    dependency.transitive_marker.without_extras().intersect(
+                        dep.marker.without_extras()
+                    )
                 )
                 if marker_intersection.is_empty():
                     # The dependency is not needed, since the markers specified
@@ -674,9 +799,9 @@ def complete_package(
 
                 dep.transitive_marker = marker_intersection
 
-            if not package.dependency.python_constraint.is_any():
+            if not dependency.python_constraint.is_any():
                 python_constraint_intersection = dep.python_constraint.intersect(
-                    package.dependency.python_constraint
+                    dependency.python_constraint
                 )
                 if python_constraint_intersection.is_empty():
                     # This dependency is not needed under current python constraint.
@@ -685,30 +810,45 @@ def complete_package(
 
             clean_dependencies.append(dep)
 
-        package.requires = clean_dependencies
+        package = package.with_dependency_groups([], only=True)
+        dependency_package = DependencyPackage(dependency, package)
 
-        return package
+        for dep in clean_dependencies:
+            package.add_dependency(dep)
+
+        return dependency_package
+
+    def get_locked(self, dependency: Dependency) -> DependencyPackage | None:
+        if dependency.name in self._use_latest:
+            return None
 
-    def debug(self, message, depth=0):
+        locked = self._locked.get(dependency.name, [])
+        for dependency_package in locked:
+            package = dependency_package.package
+            if package.satisfies(dependency):
+                return DependencyPackage(dependency, package)
+        return None
+
+    def debug(self, message: str, depth: int = 0) -> None:
         if not (self._io.is_very_verbose() or self._io.is_debug()):
             return
 
         if message.startswith("fact:"):
             if "depends on" in message:
                 m = re.match(r"fact: (.+?) depends on (.+?) \((.+?)\)", message)
+                if m is None:
+                    raise ValueError(f"Unable to parse fact: {message}")
                 m2 = re.match(r"(.+?) \((.+?)\)", m.group(1))
                 if m2:
                     name = m2.group(1)
-                    version = " ({})".format(m2.group(2))
+                    version = f" ({m2.group(2)})"
                 else:
                     name = m.group(1)
                     version = ""
 
                 message = (
-                    "fact: {}{} "
-                    "depends on {} ({})".format(
-                        name, version, m.group(2), m.group(3)
-                    )
+                    f"fact: {name}{version} "
+                    f"depends on {m.group(2)} ({m.group(3)})"
                 )
             elif " is " in message:
                 message = re.sub(
@@ -720,7 +860,7 @@ def debug(self, message, depth=0):
                 message = re.sub(
                     r"(?<=: )(.+?) \((.+?)\)", "\\1 (\\2)", message
                 )
-                message = "fact: {}".format(message.split("fact: ")[1])
+                message = f"fact: {message.split('fact: ')[1]}"
         elif message.startswith("selecting "):
             message = re.sub(
                 r"selecting (.+?) \((.+?)\)",
@@ -730,12 +870,13 @@ def debug(self, message, depth=0):
         elif message.startswith("derived:"):
             m = re.match(r"derived: (.+?) \((.+?)\)$", message)
             if m:
-                message = "derived: {} ({})".format(
-                    m.group(1), m.group(2)
+                message = (
+                    f"derived: {m.group(1)}"
+                    f" ({m.group(2)})"
                 )
             else:
-                message = "derived: {}".format(
-                    message.split("derived: ")[1]
+                message = (
+                    f"derived: {message.split('derived: ')[1]}"
                 )
         elif message.startswith("conflict:"):
             m = re.match(r"conflict: (.+?) depends on (.+?) \((.+?)\)", message)
@@ -743,20 +884,19 @@ def debug(self, message, depth=0):
                 m2 = re.match(r"(.+?) \((.+?)\)", m.group(1))
                 if m2:
                     name = m2.group(1)
-                    version = " ({})".format(m2.group(2))
+                    version = f" ({m2.group(2)})"
                 else:
                     name = m.group(1)
                     version = ""
 
                 message = (
-                    "conflict: {}{} "
-                    "depends on {} ({})".format(
-                        name, version, m.group(2), m.group(3)
-                    )
+                    f"conflict: {name}{version} "
+                    f"depends on {m.group(2)} ({m.group(3)})"
                 )
             else:
-                message = "conflict: {}".format(
-                    message.split("conflict: ")[1]
+                message = (
+                    "conflict:"
+                    f" {message.split('conflict: ')[1]}"
                 )
 
         message = message.replace("! ", "! ")
@@ -766,7 +906,7 @@ def debug(self, message, depth=0):
             debug_info = (
                 "\n".join(
                     [
-                        "{}: {}".format(str(depth).rjust(4), s)
+                        f"{str(depth).rjust(4)}: {s}"
                         for s in debug_info.split("\n")
                     ]
                 )
@@ -775,18 +915,151 @@ def debug(self, message, depth=0):
 
             self._io.write(debug_info)
 
-    @contextmanager
-    def progress(self):
-        if not self._io.output.supports_ansi() or self.is_debugging():
-            self._io.write_line("Resolving dependencies...")
-            yield
-        else:
-            indicator = Indicator(self._io, "{message} ({elapsed:2s})")
+    def _group_by_source(
+        self, dependencies: Iterable[Dependency]
+    ) -> list[list[Dependency]]:
+        """
+        Takes a list of dependencies and returns a list of groups of dependencies,
+        each group containing all dependencies from the same source.
+        """
+        groups: list[list[Dependency]] = []
+        for dep in dependencies:
+            for group in groups:
+                if (
+                    dep.is_same_source_as(group[0])
+                    and dep.source_name == group[0].source_name
+                ):
+                    group.append(dep)
+                    break
+            else:
+                groups.append([dep])
+        return groups
 
-            with indicator.auto(
-                "Resolving dependencies...",
-                "Resolving dependencies...",
-            ):
-                yield
+    def _merge_dependencies_by_constraint(
+        self, dependencies: Iterable[Dependency]
+    ) -> list[Dependency]:
+        """
+        Merge dependencies with the same constraint
+        by building a union of their markers.
+        """
+        by_constraint: dict[VersionConstraint, list[Dependency]] = defaultdict(list)
+        for dep in dependencies:
+            by_constraint[dep.constraint].append(dep)
+        for constraint, _deps in by_constraint.items():
+            new_markers = []
+            for dep in _deps:
+                marker = dep.marker.without_extras()
+                if marker.is_any():
+                    # No marker or only extras
+                    continue
+
+                new_markers.append(marker)
+
+            if not new_markers:
+                continue
+
+            dep = _deps[0]
+            dep.marker = dep.marker.union(MarkerUnion(*new_markers))
+            by_constraint[constraint] = [dep]
+
+        return [value[0] for value in by_constraint.values()]
 
-        self._in_progress = False
+    def _merge_dependencies_by_marker(
+        self, dependencies: Iterable[Dependency]
+    ) -> list[Dependency]:
+        """
+        Merge dependencies with the same marker
+        by building the intersection of their constraints.
+        """
+        by_marker: dict[BaseMarker, list[Dependency]] = defaultdict(list)
+        for dep in dependencies:
+            by_marker[dep.marker].append(dep)
+        deps = []
+        for _deps in by_marker.values():
+            if len(_deps) == 1:
+                deps.extend(_deps)
+            else:
+                new_constraint = _deps[0].constraint
+                for dep in _deps[1:]:
+                    new_constraint = new_constraint.intersect(dep.constraint)
+                if new_constraint.is_empty():
+                    # leave dependencies as-is so the resolver will pickup
+                    # the conflict and display a proper error.
+                    deps.extend(_deps)
+                else:
+                    self.debug(
+                        f"Merging constraints for {_deps[0].name} for"
+                        f" marker {_deps[0].marker}"
+                    )
+                    deps.append(_deps[0].with_constraint(new_constraint))
+        return deps
+
+    def _handle_any_marker_dependencies(
+        self, package: Package, dependencies: list[Dependency]
+    ) -> list[Dependency]:
+        """
+        We need to check if one of the duplicate dependencies
+        has no markers. If there is one, we need to change its
+        environment markers to the inverse of the union of the
+        other dependencies markers.
+        For instance, if we have the following dependencies:
+          - ipython
+          - ipython (1.2.4) ; implementation_name == "pypy"
+
+        the marker for `ipython` will become `implementation_name != "pypy"`.
+
+        Further, we have to merge the constraints of the requirements
+        without markers into the constraints of the requirements with markers.
+        for instance, if we have the following dependencies:
+          - foo (>= 1.2)
+          - foo (!= 1.2.1) ; python == 3.10
+
+        the constraint for the second entry will become (!= 1.2.1, >= 1.2).
+        """
+        any_markers_dependencies = [d for d in dependencies if d.marker.is_any()]
+        other_markers_dependencies = [d for d in dependencies if not d.marker.is_any()]
+
+        if any_markers_dependencies:
+            for dep_other in other_markers_dependencies:
+                new_constraint = dep_other.constraint
+                for dep_any in any_markers_dependencies:
+                    new_constraint = new_constraint.intersect(dep_any.constraint)
+                if new_constraint.is_empty():
+                    raise IncompatibleConstraintsError(
+                        package, dep_other, *any_markers_dependencies
+                    )
+                dep_other.constraint = new_constraint
+
+        marker = other_markers_dependencies[0].marker
+        for other_dep in other_markers_dependencies[1:]:
+            marker = marker.union(other_dep.marker)
+        inverted_marker = marker.invert()
+
+        if (
+            not inverted_marker.is_empty()
+            and self._python_constraint.allows_any(
+                get_python_constraint_from_marker(inverted_marker)
+            )
+            and (not self._env or inverted_marker.validate(self._env.marker_env))
+        ):
+            if any_markers_dependencies:
+                for dep_any in any_markers_dependencies:
+                    dep_any.marker = inverted_marker
+            else:
+                # If there is no any marker dependency
+                # and the inverted marker is not empty,
+                # a dependency with the inverted union of all markers is required
+                # in order to not miss other dependencies later, for instance:
+                #   - foo (1.0) ; python == 3.7
+                #   - foo (2.0) ; python == 3.8
+                #   - bar (2.0) ; python == 3.8
+                #   - bar (3.0) ; python == 3.9
+                #
+                # the last dependency would be missed without this,
+                # because the intersection with both foo dependencies is empty.
+                inverted_marker_dep = dependencies[0].with_constraint(EmptyConstraint())
+                inverted_marker_dep.marker = inverted_marker
+                dependencies.append(inverted_marker_dep)
+        else:
+            dependencies = other_markers_dependencies
+        return dependencies
diff --git a/conda_lock/_vendor/poetry/puzzle/solver.py b/conda_lock/_vendor/poetry/puzzle/solver.py
index f63e8247a..0165ab481 100644
--- a/conda_lock/_vendor/poetry/puzzle/solver.py
+++ b/conda_lock/_vendor/poetry/puzzle/solver.py
@@ -1,210 +1,138 @@
-import enum
+from __future__ import annotations
+
 import time
 
 from collections import defaultdict
 from contextlib import contextmanager
-from typing import List
-from typing import Optional
+from typing import TYPE_CHECKING
+from typing import Collection
+from typing import FrozenSet
+from typing import Tuple
+from typing import TypeVar
 
-from clikit.io import ConsoleIO
+from conda_lock._vendor.poetry.core.packages.dependency_group import MAIN_GROUP
 
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
-from conda_lock._vendor.poetry.installation.operations import Install
-from conda_lock._vendor.poetry.installation.operations import Uninstall
-from conda_lock._vendor.poetry.installation.operations import Update
-from conda_lock._vendor.poetry.installation.operations.operation import Operation
 from conda_lock._vendor.poetry.mixology import resolve_version
 from conda_lock._vendor.poetry.mixology.failure import SolveFailure
-from conda_lock._vendor.poetry.packages import DependencyPackage
-from conda_lock._vendor.poetry.repositories import Pool
-from conda_lock._vendor.poetry.repositories import Repository
-from conda_lock._vendor.poetry.utils.env import Env
+from conda_lock._vendor.poetry.puzzle.exceptions import OverrideNeeded
+from conda_lock._vendor.poetry.puzzle.exceptions import SolverProblemError
+from conda_lock._vendor.poetry.puzzle.provider import Indicator
+from conda_lock._vendor.poetry.puzzle.provider import Provider
+
+
+if TYPE_CHECKING:
+    from collections.abc import Iterator
+
+    from conda_lock._vendor.cleo.io.io import IO
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from conda_lock._vendor.poetry.core.packages.project_package import ProjectPackage
 
-from .exceptions import OverrideNeeded
-from .exceptions import SolverProblemError
-from .provider import Provider
+    from conda_lock._vendor.poetry.packages import DependencyPackage
+    from conda_lock._vendor.poetry.puzzle.transaction import Transaction
+    from conda_lock._vendor.poetry.repositories import RepositoryPool
+    from conda_lock._vendor.poetry.utils.env import Env
 
 
 class Solver:
     def __init__(
         self,
-        package,  # type: ProjectPackage
-        pool,  # type: Pool
-        installed,  # type: Repository
-        locked,  # type: Repository
-        io,  # type: ConsoleIO
-        remove_untracked=False,  # type: bool
-        provider=None,  # type: Optional[Provider]
-    ):
+        package: ProjectPackage,
+        pool: RepositoryPool,
+        installed: list[Package],
+        locked: list[Package],
+        io: IO,
+    ) -> None:
         self._package = package
         self._pool = pool
-        self._installed = installed
-        self._locked = locked
+        self._installed_packages = installed
+        self._locked_packages = locked
         self._io = io
 
-        if provider is None:
-            provider = Provider(self._package, self._pool, self._io)
-
-        self._provider = provider
-        self._overrides = []
-        self._remove_untracked = remove_untracked
+        self._provider = Provider(
+            self._package, self._pool, self._io, installed=installed, locked=locked
+        )
+        self._overrides: list[dict[DependencyPackage, dict[str, Dependency]]] = []
 
     @property
-    def provider(self):  # type: () -> Provider
+    def provider(self) -> Provider:
         return self._provider
 
     @contextmanager
-    def use_environment(self, env):  # type: (Env) -> None
+    def use_environment(self, env: Env) -> Iterator[None]:
         with self.provider.use_environment(env):
             yield
 
-    def solve(self, use_latest=None):  # type: (...) -> List[Operation]
-        with self._provider.progress():
+    def solve(
+        self, use_latest: Collection[NormalizedName] | None = None
+    ) -> Transaction:
+        from conda_lock._vendor.poetry.puzzle.transaction import Transaction
+
+        with self._progress(), self._provider.use_latest_for(use_latest or []):
             start = time.time()
-            packages, depths = self._solve(use_latest=use_latest)
+            packages, depths = self._solve()
             end = time.time()
 
             if len(self._overrides) > 1:
                 self._provider.debug(
-                    "Complete version solving took {:.3f} seconds with {} overrides".format(
-                        end - start, len(self._overrides)
-                    )
+                    # ignore the warning as provider does not do interpolation
+                    f"Complete version solving took {end - start:.3f}"  # noqa: PIE803
+                    f" seconds with {len(self._overrides)} overrides"
                 )
                 self._provider.debug(
-                    "Resolved with overrides: {}".format(
-                        ", ".join("({})".format(b) for b in self._overrides)
-                    )
+                    # ignore the warning as provider does not do interpolation
+                    "Resolved with overrides:"  # noqa: PIE803
+                    f" {', '.join(f'({b})' for b in self._overrides)}"
                 )
 
-        operations = []
-        for i, package in enumerate(packages):
-            installed = False
-            for pkg in self._installed.packages:
-                if package.name == pkg.name:
-                    installed = True
-
-                    if pkg.source_type == "git" and package.source_type == "git":
-                        from conda_lock._vendor.poetry.core.vcs.git import Git
-
-                        # 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:
-                            if locked.name != pkg.name or locked.source_type != "git":
-                                continue
-
-                            locked_source_url = Git.normalize_url(locked.source_url)
-                            if (
-                                locked.name == pkg.name
-                                and locked.source_type == pkg.source_type
-                                and locked_source_url == pkg_source_url
-                                and locked.source_reference == pkg.source_reference
-                                and locked.source_resolved_reference
-                                == pkg.source_resolved_reference
-                            ):
-                                pkg = Package(
-                                    pkg.name,
-                                    locked.version,
-                                    source_type="git",
-                                    source_url=locked.source_url,
-                                    source_reference=locked.source_reference,
-                                    source_resolved_reference=locked.source_resolved_reference,
-                                )
-                                break
-
-                        if pkg_source_url != package_source_url or (
-                            (
-                                not pkg.source_resolved_reference
-                                or not package.source_resolved_reference
-                            )
-                            and pkg.source_reference != package.source_reference
-                            and not pkg.source_reference.startswith(
-                                package.source_reference
-                            )
-                            or (
-                                pkg.source_resolved_reference
-                                and package.source_resolved_reference
-                                and pkg.source_resolved_reference
-                                != package.source_resolved_reference
-                                and not pkg.source_resolved_reference.startswith(
-                                    package.source_resolved_reference
-                                )
-                            )
-                        ):
-                            operations.append(Update(pkg, package, priority=depths[i]))
-                        else:
-                            operations.append(
-                                Install(package).skip("Already installed")
-                            )
-                    elif package.version != pkg.version:
-                        # Checking version
-                        operations.append(Update(pkg, package, priority=depths[i]))
-                    elif pkg.source_type and package.source_type != pkg.source_type:
-                        operations.append(Update(pkg, package, priority=depths[i]))
-                    else:
-                        operations.append(
-                            Install(package, priority=depths[i]).skip(
-                                "Already installed"
-                            )
-                        )
-
-                    break
-
-            if not installed:
-                operations.append(Install(package, priority=depths[i]))
-
-        # Checking for removals
-        for pkg in self._locked.packages:
-            remove = True
-            for package in packages:
-                if pkg.name == package.name:
-                    remove = False
-                    break
-
-            if remove:
-                skip = True
-                for installed in self._installed.packages:
-                    if installed.name == pkg.name:
-                        skip = False
-                        break
-
-                op = Uninstall(pkg)
-                if skip:
-                    op.skip("Not currently installed")
-
-                operations.append(op)
-
-        if self._remove_untracked:
-            locked_names = {locked.name for locked in self._locked.packages}
-
-            for installed in self._installed.packages:
-                if installed.name == self._package.name:
-                    continue
-                if installed.name in Provider.UNSAFE_PACKAGES:
-                    # Never remove pip, setuptools etc.
-                    continue
-                if installed.name not in locked_names:
-                    operations.append(Uninstall(installed))
-
-        return sorted(
-            operations, key=lambda o: (-o.priority, o.package.name, o.package.version,),
+        for p in packages:
+            if p.yanked:
+                message = (
+                    f"The locked version {p.pretty_version} for {p.pretty_name} is a"
+                    " yanked version."
+                )
+                if p.yanked_reason:
+                    message += f" Reason for being yanked: {p.yanked_reason}"
+                self._io.write_error_line(f"Warning: {message}")
+
+        return Transaction(
+            self._locked_packages,
+            list(zip(packages, depths)),
+            installed_packages=self._installed_packages,
+            root_package=self._package,
         )
 
-    def solve_in_compatibility_mode(self, overrides, use_latest=None):
-        locked = {}
-        for package in self._locked.packages:
-            locked[package.name] = DependencyPackage(package.to_dependency(), package)
+    @contextmanager
+    def _progress(self) -> Iterator[None]:
+        if not self._io.output.is_decorated() or self._provider.is_debugging():
+            self._io.write_line("Resolving dependencies...")
+            yield
+        else:
+            indicator = Indicator(
+                self._io, "{message}{context}({elapsed:2s})"
+            )
 
+            with indicator.auto(
+                "Resolving dependencies...",
+                "Resolving dependencies...",
+            ):
+                yield
+
+    def _solve_in_compatibility_mode(
+        self,
+        overrides: tuple[dict[DependencyPackage, dict[str, Dependency]], ...],
+    ) -> tuple[list[Package], list[int]]:
         packages = []
         depths = []
         for override in overrides:
             self._provider.debug(
-                "Retrying dependency resolution "
-                "with the following overrides ({}).".format(override)
+                # ignore the warning as provider does not do interpolation
+                "Retrying dependency resolution "  # noqa: PIE803
+                f"with the following overrides ({override})."
             )
             self._provider.set_overrides(override)
-            _packages, _depths = self._solve(use_latest=use_latest)
+            _packages, _depths = self._solve()
             for index, package in enumerate(_packages):
                 if package not in packages:
                     packages.append(package)
@@ -217,34 +145,25 @@ def solve_in_compatibility_mode(self, overrides, use_latest=None):
 
                     for dep in package.requires:
                         if dep not in pkg.requires:
-                            pkg.requires.append(dep)
+                            pkg.add_dependency(dep)
 
         return packages, depths
 
-    def _solve(self, use_latest=None):
+    def _solve(self) -> tuple[list[Package], list[int]]:
         if self._provider._overrides:
             self._overrides.append(self._provider._overrides)
 
-        locked = {}
-        for package in self._locked.packages:
-            locked[package.name] = DependencyPackage(package.to_dependency(), package)
-
         try:
-            result = resolve_version(
-                self._package, self._provider, locked=locked, use_latest=use_latest
-            )
+            result = resolve_version(self._package, self._provider)
 
             packages = result.packages
         except OverrideNeeded as e:
-            return self.solve_in_compatibility_mode(e.overrides, use_latest=use_latest)
+            return self._solve_in_compatibility_mode(e.overrides)
         except SolveFailure as e:
             raise SolverProblemError(e)
 
-        results = dict(
-            depth_first_search(
-                PackageNode(self._package, packages), aggregate_package_nodes
-            )
-        )
+        combined_nodes = depth_first_search(PackageNode(self._package, packages))
+        results = dict(aggregate_package_nodes(nodes) for nodes in combined_nodes)
 
         # Merging feature packages with base packages
         final_packages = []
@@ -253,167 +172,135 @@ def _solve(self, use_latest=None):
             if package.features:
                 for _package in packages:
                     if (
-                        _package.name == package.name
-                        and not _package.is_same_package_as(package)
+                        not _package.features
+                        and _package.name == package.name
                         and _package.version == package.version
                     ):
                         for dep in package.requires:
-                            if dep.is_same_package_as(_package):
+                            # Prevent adding base package as a dependency to itself
+                            if _package.name == dep.name:
                                 continue
 
                             if dep not in _package.requires:
-                                _package.requires.append(dep)
-
-                continue
-
-            final_packages.append(package)
-            depths.append(results[package])
+                                _package.add_dependency(dep)
+            else:
+                final_packages.append(package)
+                depths.append(results[package])
 
         # Return the packages in their original order with associated depths
         return final_packages, depths
 
 
-class DFSNode(object):
-    def __init__(self, id, name, base_name):
+DFSNodeID = Tuple[str, FrozenSet[str], bool]
+
+T = TypeVar("T", bound="DFSNode")
+
+
+class DFSNode:
+    def __init__(self, id: DFSNodeID, name: str, base_name: str) -> None:
         self.id = id
         self.name = name
         self.base_name = base_name
 
-    def reachable(self):
+    def reachable(self: T) -> list[T]:
         return []
 
-    def visit(self, parents):
+    def visit(self, parents: list[PackageNode]) -> None:
         pass
 
-    def __str__(self):
+    def __str__(self) -> str:
         return str(self.id)
 
 
-class VisitedState(enum.Enum):
-    Unvisited = 0
-    PartiallyVisited = 1
-    Visited = 2
-
-
-def depth_first_search(source, aggregator):
-    back_edges = defaultdict(list)
-    visited = {}
-    topo_sorted_nodes = []
+def depth_first_search(source: PackageNode) -> list[list[PackageNode]]:
+    back_edges: dict[DFSNodeID, list[PackageNode]] = defaultdict(list)
+    visited: set[DFSNodeID] = set()
+    topo_sorted_nodes: list[PackageNode] = []
 
     dfs_visit(source, back_edges, visited, topo_sorted_nodes)
 
     # Combine the nodes by name
-    combined_nodes = defaultdict(list)
-    name_children = defaultdict(list)
+    combined_nodes: dict[str, list[PackageNode]] = defaultdict(list)
     for node in topo_sorted_nodes:
         node.visit(back_edges[node.id])
-        name_children[node.name].extend(node.reachable())
         combined_nodes[node.name].append(node)
 
-    combined_topo_sorted_nodes = []
-    for node in topo_sorted_nodes:
-        if node.name in combined_nodes:
-            combined_topo_sorted_nodes.append(combined_nodes.pop(node.name))
-
-    results = [
-        aggregator(nodes, name_children[nodes[0].name])
-        for nodes in combined_topo_sorted_nodes
+    combined_topo_sorted_nodes: list[list[PackageNode]] = [
+        combined_nodes.pop(node.name)
+        for node in topo_sorted_nodes
+        if node.name in combined_nodes
     ]
-    return results
+
+    return combined_topo_sorted_nodes
 
 
-def dfs_visit(node, back_edges, visited, sorted_nodes):
-    if visited.get(node.id, VisitedState.Unvisited) == VisitedState.Visited:
-        return True
-    if visited.get(node.id, VisitedState.Unvisited) == VisitedState.PartiallyVisited:
-        # We have a circular dependency.
-        # Since the dependencies are resolved we can
-        # simply skip it because we already have it
-        return True
+def dfs_visit(
+    node: PackageNode,
+    back_edges: dict[DFSNodeID, list[PackageNode]],
+    visited: set[DFSNodeID],
+    sorted_nodes: list[PackageNode],
+) -> None:
+    if node.id in visited:
+        return
+    visited.add(node.id)
 
-    visited[node.id] = VisitedState.PartiallyVisited
     for neighbor in node.reachable():
         back_edges[neighbor.id].append(node)
-        if not dfs_visit(neighbor, back_edges, visited, sorted_nodes):
-            return False
-    visited[node.id] = VisitedState.Visited
+        dfs_visit(neighbor, back_edges, visited, sorted_nodes)
     sorted_nodes.insert(0, node)
-    return True
 
 
 class PackageNode(DFSNode):
     def __init__(
-        self, package, packages, previous=None, previous_dep=None, dep=None,
-    ):
+        self,
+        package: Package,
+        packages: list[Package],
+        previous: PackageNode | None = None,
+        dep: Dependency | None = None,
+    ) -> None:
         self.package = package
         self.packages = packages
 
-        self.previous = previous
-        self.previous_dep = previous_dep
         self.dep = dep
         self.depth = -1
 
         if not previous:
             self.category = "dev"
+            self.groups: frozenset[str] = frozenset()
             self.optional = True
-        else:
-            self.category = dep.category
+        elif dep:
+            self.category = "main" if MAIN_GROUP in dep.groups else "dev"
+            self.groups = dep.groups
             self.optional = dep.is_optional()
+        else:
+            raise ValueError("Both previous and dep must be passed")
 
-        super(PackageNode, self).__init__(
-            (package.complete_name, self.category, self.optional),
+        super().__init__(
+            (package.complete_name, self.groups, self.optional),
             package.complete_name,
             package.name,
         )
 
-    def reachable(self):
-        children = []  # type: List[PackageNode]
-
-        if (
-            self.previous_dep
-            and self.previous_dep is not self.dep
-            and self.previous_dep.name == self.dep.name
-        ):
-            return []
+    def reachable(self) -> list[PackageNode]:
+        children: list[PackageNode] = []
 
         for dependency in self.package.all_requires:
-            if self.previous and self.previous.name == dependency.name:
-                # We have a circular dependency.
-                # Since the dependencies are resolved we can
-                # simply skip it because we already have it
-                # N.B. this only catches cycles of length 2;
-                # dependency cycles in general are handled by the DFS traversal
-                continue
-
             for pkg in self.packages:
                 if pkg.complete_name == dependency.complete_name and (
                     dependency.constraint.allows(pkg.version)
-                    or dependency.allows_prereleases()
-                    and pkg.version.is_prerelease()
-                    and dependency.constraint.allows(pkg.version.stable)
                 ):
-                    # If there is already a child with this name
-                    # we merge the requirements
-                    if any(
-                        child.package.name == pkg.name
-                        and child.category == dependency.category
-                        for child in children
-                    ):
-                        continue
-
                     children.append(
                         PackageNode(
                             pkg,
                             self.packages,
                             self,
-                            dependency,
                             self.dep or dependency,
                         )
                     )
 
         return children
 
-    def visit(self, parents):
+    def visit(self, parents: list[PackageNode]) -> None:
         # The root package, which has no parents, is defined as having depth -1
         # So that the root package's top-level dependencies have depth 0.
         self.depth = 1 + max(
@@ -425,17 +312,21 @@ def visit(self, parents):
         )
 
 
-def aggregate_package_nodes(nodes, children):
+def aggregate_package_nodes(nodes: list[PackageNode]) -> tuple[Package, int]:
     package = nodes[0].package
     depth = max(node.depth for node in nodes)
-    category = (
-        "main" if any(node.category == "main" for node in children + nodes) else "dev"
-    )
-    optional = all(node.optional for node in children + nodes)
+    groups: list[str] = []
+    for node in nodes:
+        groups.extend(node.groups)
+
+    category = "main" if any(MAIN_GROUP in node.groups for node in nodes) else "dev"
+    optional = all(node.optional for node in nodes)
     for node in nodes:
         node.depth = depth
         node.category = category
         node.optional = optional
+
     package.category = category
     package.optional = optional
+
     return package, depth
diff --git a/conda_lock/_vendor/poetry/puzzle/transaction.py b/conda_lock/_vendor/poetry/puzzle/transaction.py
new file mode 100644
index 000000000..7aad81bec
--- /dev/null
+++ b/conda_lock/_vendor/poetry/puzzle/transaction.py
@@ -0,0 +1,121 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.installation.operations.operation import Operation
+
+
+class Transaction:
+    def __init__(
+        self,
+        current_packages: list[Package],
+        result_packages: list[tuple[Package, int]],
+        installed_packages: list[Package] | None = None,
+        root_package: Package | None = None,
+    ) -> None:
+        self._current_packages = current_packages
+        self._result_packages = result_packages
+
+        if installed_packages is None:
+            installed_packages = []
+
+        self._installed_packages = installed_packages
+        self._root_package = root_package
+
+    def calculate_operations(
+        self, with_uninstalls: bool = True, synchronize: bool = False
+    ) -> list[Operation]:
+        from conda_lock._vendor.poetry.installation.operations import Install
+        from conda_lock._vendor.poetry.installation.operations import Uninstall
+        from conda_lock._vendor.poetry.installation.operations import Update
+
+        operations: list[Operation] = []
+
+        for result_package, priority in self._result_packages:
+            installed = False
+
+            for installed_package in self._installed_packages:
+                if result_package.name == installed_package.name:
+                    installed = True
+
+                    # We have to perform an update if the version or another
+                    # attribute of the package has changed (source type, url, ref, ...).
+                    if result_package.version != installed_package.version or (
+                        (
+                            # This has to be done because installed packages cannot
+                            # have type "legacy". If a package with type "legacy"
+                            # is installed, the installed package has no source_type.
+                            # Thus, if installed_package has no source_type and
+                            # the result_package has source_type "legacy" (negation of
+                            # the following condition), update must not be performed.
+                            # This quirk has the side effect that when switching
+                            # from PyPI to legacy (or vice versa),
+                            # no update is performed.
+                            installed_package.source_type
+                            or result_package.source_type != "legacy"
+                        )
+                        and not result_package.is_same_package_as(installed_package)
+                    ):
+                        operations.append(
+                            Update(installed_package, result_package, priority=priority)
+                        )
+                    else:
+                        operations.append(
+                            Install(result_package).skip("Already installed")
+                        )
+
+                    break
+
+            if not installed:
+                operations.append(Install(result_package, priority=priority))
+
+        if with_uninstalls:
+            for current_package in self._current_packages:
+                found = any(
+                    current_package.name == result_package.name
+                    for result_package, _ in self._result_packages
+                )
+
+                if not found:
+                    for installed_package in self._installed_packages:
+                        if installed_package.name == current_package.name:
+                            operations.append(Uninstall(current_package))
+
+            if synchronize:
+                current_package_names = {
+                    current_package.name for current_package in self._current_packages
+                }
+                # We preserve pip/setuptools/wheel when not managed by poetry, this is
+                # done to avoid externally managed virtual environments causing
+                # unnecessary removals.
+                preserved_package_names = {
+                    "pip",
+                    "setuptools",
+                    "wheel",
+                } - current_package_names
+
+                for installed_package in self._installed_packages:
+                    if (
+                        self._root_package
+                        and installed_package.name == self._root_package.name
+                    ):
+                        continue
+
+                    if installed_package.name in preserved_package_names:
+                        continue
+
+                    if installed_package.name not in current_package_names:
+                        operations.append(Uninstall(installed_package))
+
+        return sorted(
+            operations,
+            key=lambda o: (
+                -o.priority,
+                o.package.name,
+                o.package.version,
+            ),
+        )
diff --git a/conda_lock/_vendor/poetry/py.typed b/conda_lock/_vendor/poetry/py.typed
new file mode 100644
index 000000000..e69de29bb
diff --git a/conda_lock/_vendor/poetry/repositories/__init__.py b/conda_lock/_vendor/poetry/repositories/__init__.py
index ab92fb11b..39e3aad43 100644
--- a/conda_lock/_vendor/poetry/repositories/__init__.py
+++ b/conda_lock/_vendor/poetry/repositories/__init__.py
@@ -1,2 +1,8 @@
-from .pool import Pool
-from .repository import Repository
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.repositories.pool import Pool
+from conda_lock._vendor.poetry.repositories.repository import Repository
+from conda_lock._vendor.poetry.repositories.repository_pool import RepositoryPool
+
+
+__all__ = ["Pool", "Repository", "RepositoryPool"]
diff --git a/conda_lock/_vendor/poetry/repositories/abstract_repository.py b/conda_lock/_vendor/poetry/repositories/abstract_repository.py
new file mode 100644
index 000000000..0dc75aa0a
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/abstract_repository.py
@@ -0,0 +1,37 @@
+from __future__ import annotations
+
+from abc import ABC
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.constraints.version import Version
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+
+class AbstractRepository(ABC):
+    def __init__(self, name: str) -> None:
+        self._name = name
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @abstractmethod
+    def find_packages(self, dependency: Dependency) -> list[Package]:
+        ...
+
+    @abstractmethod
+    def search(self, query: str) -> list[Package]:
+        ...
+
+    @abstractmethod
+    def package(
+        self,
+        name: str,
+        version: Version,
+        extras: list[str] | None = None,
+    ) -> Package:
+        ...
diff --git a/conda_lock/_vendor/poetry/repositories/base_repository.py b/conda_lock/_vendor/poetry/repositories/base_repository.py
deleted file mode 100644
index 46422ca0e..000000000
--- a/conda_lock/_vendor/poetry/repositories/base_repository.py
+++ /dev/null
@@ -1,19 +0,0 @@
-class BaseRepository(object):
-    def __init__(self):
-        self._packages = []
-
-    @property
-    def packages(self):
-        return self._packages
-
-    def has_package(self, package):
-        raise NotImplementedError()
-
-    def package(self, name, version, extras=None):
-        raise NotImplementedError()
-
-    def find_packages(self, dependency):
-        raise NotImplementedError()
-
-    def search(self, query):
-        raise NotImplementedError()
diff --git a/conda_lock/_vendor/poetry/repositories/cached.py b/conda_lock/_vendor/poetry/repositories/cached.py
new file mode 100644
index 000000000..a0c18e0d8
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/cached.py
@@ -0,0 +1,15 @@
+from __future__ import annotations
+
+import warnings
+
+
+from conda_lock._vendor.poetry.repositories.cached_repository import (  # isort: skip # nopycln: import # noqa: E501, F401
+    CachedRepository,
+)
+
+warnings.warn(
+    "Module poetry.repositories.cached is renamed and scheduled for removal in poetry"
+    " release 1.4.0. Please migrate to poetry.repositories.cached_repository.",
+    DeprecationWarning,
+    stacklevel=2,
+)
diff --git a/conda_lock/_vendor/poetry/repositories/cached_repository.py b/conda_lock/_vendor/poetry/repositories/cached_repository.py
new file mode 100644
index 000000000..343c4b405
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/cached_repository.py
@@ -0,0 +1,78 @@
+from __future__ import annotations
+
+from abc import ABC
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+from typing import Any
+
+from packaging.utils import canonicalize_name
+from conda_lock._vendor.poetry.core.constraints.version import parse_constraint
+
+from conda_lock._vendor.poetry.config.config import Config
+from conda_lock._vendor.poetry.repositories.repository import Repository
+from conda_lock._vendor.poetry.utils.cache import FileCache
+
+
+if TYPE_CHECKING:
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.constraints.version import Version
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.inspection.info import PackageInfo
+
+
+class CachedRepository(Repository, ABC):
+    CACHE_VERSION = parse_constraint("1.1.0")
+
+    def __init__(
+        self, name: str, disable_cache: bool = False, config: Config | None = None
+    ) -> None:
+        super().__init__(name)
+        self._disable_cache = disable_cache
+        self._cache_dir = (config or Config.create()).repository_cache_directory / name
+        self._release_cache: FileCache[dict[str, Any]] = FileCache(path=self._cache_dir)
+
+    @abstractmethod
+    def _get_release_info(
+        self, name: NormalizedName, version: Version
+    ) -> dict[str, Any]:
+        ...
+
+    def get_release_info(self, name: NormalizedName, version: Version) -> PackageInfo:
+        """
+        Return the release information given a package name and a version.
+
+        The information is returned from the cache if it exists
+        or retrieved from the remote server.
+        """
+        from conda_lock._vendor.poetry.inspection.info import PackageInfo
+
+        if self._disable_cache:
+            return PackageInfo.load(self._get_release_info(name, version))
+
+        cached = self._release_cache.remember(
+            f"{name}:{version}", lambda: self._get_release_info(name, version)
+        )
+
+        cache_version = cached.get("_cache_version", "0.0.0")
+        if parse_constraint(cache_version) != self.CACHE_VERSION:
+            # The cache must be updated
+            self._log(
+                f"The cache for {name} {version} is outdated. Refreshing.",
+                level="debug",
+            )
+            cached = self._get_release_info(name, version)
+
+            self._release_cache.put(f"{name}:{version}", cached)
+
+        return PackageInfo.load(cached)
+
+    def package(
+        self,
+        name: str,
+        version: Version,
+        extras: list[str] | None = None,
+    ) -> Package:
+        return self.get_release_info(canonicalize_name(name), version).to_package(
+            name=name, extras=extras
+        )
diff --git a/conda_lock/_vendor/poetry/repositories/exceptions.py b/conda_lock/_vendor/poetry/repositories/exceptions.py
index 170303f39..10ad3c460 100644
--- a/conda_lock/_vendor/poetry/repositories/exceptions.py
+++ b/conda_lock/_vendor/poetry/repositories/exceptions.py
@@ -1,8 +1,9 @@
-class RepositoryError(Exception):
+from __future__ import annotations
+
 
+class RepositoryError(Exception):
     pass
 
 
 class PackageNotFound(Exception):
-
     pass
diff --git a/conda_lock/_vendor/poetry/repositories/http.py b/conda_lock/_vendor/poetry/repositories/http.py
new file mode 100644
index 000000000..99b0c0720
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/http.py
@@ -0,0 +1,15 @@
+from __future__ import annotations
+
+import warnings
+
+
+from conda_lock._vendor.poetry.repositories.http_repository import (  # isort: skip # nopycln: import # noqa: E501, F401
+    HTTPRepository,
+)
+
+warnings.warn(
+    "Module poetry.repositories.http is renamed and scheduled for removal in poetry"
+    " release 1.4.0. Please migrate to poetry.repositories.http_repository.",
+    DeprecationWarning,
+    stacklevel=2,
+)
diff --git a/conda_lock/_vendor/poetry/repositories/http_repository.py b/conda_lock/_vendor/poetry/repositories/http_repository.py
new file mode 100644
index 000000000..f3d7a4cab
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/http_repository.py
@@ -0,0 +1,303 @@
+from __future__ import annotations
+
+import functools
+import hashlib
+import os
+import urllib
+import urllib.parse
+
+from collections import defaultdict
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Any
+
+import requests
+
+from conda_lock._vendor.poetry.core.constraints.version import parse_constraint
+from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+from conda_lock._vendor.poetry.core.packages.utils.link import Link
+from conda_lock._vendor.poetry.core.utils.helpers import temporary_directory
+from conda_lock._vendor.poetry.core.version.markers import parse_marker
+
+from conda_lock._vendor.poetry.repositories.cached_repository import CachedRepository
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
+from conda_lock._vendor.poetry.repositories.exceptions import RepositoryError
+from conda_lock._vendor.poetry.repositories.link_sources.html import HTMLPage
+from conda_lock._vendor.poetry.utils.authenticator import Authenticator
+from conda_lock._vendor.poetry.utils.constants import REQUESTS_TIMEOUT
+from conda_lock._vendor.poetry.utils.helpers import download_file
+from conda_lock._vendor.poetry.utils.patterns import wheel_file_re
+
+
+if TYPE_CHECKING:
+    from packaging.utils import NormalizedName
+
+    from conda_lock._vendor.poetry.config.config import Config
+    from conda_lock._vendor.poetry.inspection.info import PackageInfo
+    from conda_lock._vendor.poetry.repositories.link_sources.base import LinkSource
+    from conda_lock._vendor.poetry.utils.authenticator import RepositoryCertificateConfig
+
+
+class HTTPRepository(CachedRepository):
+    def __init__(
+        self,
+        name: str,
+        url: str,
+        config: Config | None = None,
+        disable_cache: bool = False,
+    ) -> None:
+        super().__init__(name, disable_cache, config)
+        self._url = url
+        self._authenticator = Authenticator(
+            config=config,
+            cache_id=name,
+            disable_cache=disable_cache,
+        )
+        self._authenticator.add_repository(name, url)
+        self.get_page = functools.lru_cache(maxsize=None)(self._get_page)
+
+    @property
+    def session(self) -> Authenticator:
+        return self._authenticator
+
+    @property
+    def url(self) -> str:
+        return self._url
+
+    @property
+    def certificates(self) -> RepositoryCertificateConfig:
+        return self._authenticator.get_certs_for_url(self.url)
+
+    @property
+    def authenticated_url(self) -> str:
+        return self._authenticator.authenticated_url(url=self.url)
+
+    def _download(self, url: str, dest: Path) -> None:
+        return download_file(url, dest, session=self.session)
+
+    def _get_info_from_wheel(self, url: str) -> PackageInfo:
+        from conda_lock._vendor.poetry.inspection.info import PackageInfo
+
+        wheel_name = urllib.parse.urlparse(url).path.rsplit("/")[-1]
+        self._log(f"Downloading wheel: {wheel_name}", level="debug")
+
+        filename = os.path.basename(wheel_name)
+
+        with temporary_directory() as temp_dir:
+            filepath = Path(temp_dir) / filename
+            self._download(url, filepath)
+
+            return PackageInfo.from_wheel(filepath)
+
+    def _get_info_from_sdist(self, url: str) -> PackageInfo:
+        from conda_lock._vendor.poetry.inspection.info import PackageInfo
+
+        sdist_name = urllib.parse.urlparse(url).path
+        sdist_name_log = sdist_name.rsplit("/")[-1]
+
+        self._log(f"Downloading sdist: {sdist_name_log}", level="debug")
+
+        filename = os.path.basename(sdist_name)
+
+        with temporary_directory() as temp_dir:
+            filepath = Path(temp_dir) / filename
+            self._download(url, filepath)
+
+            return PackageInfo.from_sdist(filepath)
+
+    def _get_info_from_urls(self, urls: dict[str, list[str]]) -> PackageInfo:
+        # Prefer to read data from wheels: this is faster and more reliable
+        wheels = urls.get("bdist_wheel")
+        if wheels:
+            # We ought just to be able to look at any of the available wheels to read
+            # metadata, they all should give the same answer.
+            #
+            # In practice this hasn't always been true.
+            #
+            # Most of the code in here is to deal with cases such as isort 4.3.4 which
+            # published separate python3 and python2 wheels with quite different
+            # dependencies.  We try to detect such cases and combine the data from the
+            # two wheels into what ought to have been published in the first place...
+            universal_wheel = None
+            universal_python2_wheel = None
+            universal_python3_wheel = None
+            platform_specific_wheels = []
+            for wheel in wheels:
+                link = Link(wheel)
+                m = wheel_file_re.match(link.filename)
+                if not m:
+                    continue
+
+                pyver = m.group("pyver")
+                abi = m.group("abi")
+                plat = m.group("plat")
+                if abi == "none" and plat == "any":
+                    # Universal wheel
+                    if pyver == "py2.py3":
+                        # Any Python
+                        universal_wheel = wheel
+                    elif pyver == "py2":
+                        universal_python2_wheel = wheel
+                    else:
+                        universal_python3_wheel = wheel
+                else:
+                    platform_specific_wheels.append(wheel)
+
+            if universal_wheel is not None:
+                return self._get_info_from_wheel(universal_wheel)
+
+            info = None
+            if universal_python2_wheel and universal_python3_wheel:
+                info = self._get_info_from_wheel(universal_python2_wheel)
+
+                py3_info = self._get_info_from_wheel(universal_python3_wheel)
+
+                if info.requires_python or py3_info.requires_python:
+                    info.requires_python = str(
+                        parse_constraint(info.requires_python or "^2.7").union(
+                            parse_constraint(py3_info.requires_python or "^3")
+                        )
+                    )
+
+                if py3_info.requires_dist:
+                    if not info.requires_dist:
+                        info.requires_dist = py3_info.requires_dist
+
+                        return info
+
+                    py2_requires_dist = {
+                        Dependency.create_from_pep_508(r).to_pep_508()
+                        for r in info.requires_dist
+                    }
+                    py3_requires_dist = {
+                        Dependency.create_from_pep_508(r).to_pep_508()
+                        for r in py3_info.requires_dist
+                    }
+                    base_requires_dist = py2_requires_dist & py3_requires_dist
+                    py2_only_requires_dist = py2_requires_dist - py3_requires_dist
+                    py3_only_requires_dist = py3_requires_dist - py2_requires_dist
+
+                    # Normalizing requires_dist
+                    requires_dist = list(base_requires_dist)
+                    for requirement in py2_only_requires_dist:
+                        dep = Dependency.create_from_pep_508(requirement)
+                        dep.marker = dep.marker.intersect(
+                            parse_marker("python_version == '2.7'")
+                        )
+                        requires_dist.append(dep.to_pep_508())
+
+                    for requirement in py3_only_requires_dist:
+                        dep = Dependency.create_from_pep_508(requirement)
+                        dep.marker = dep.marker.intersect(
+                            parse_marker("python_version >= '3'")
+                        )
+                        requires_dist.append(dep.to_pep_508())
+
+                    info.requires_dist = sorted(set(requires_dist))
+
+            if info:
+                return info
+
+            # Prefer non platform specific wheels
+            if universal_python3_wheel:
+                return self._get_info_from_wheel(universal_python3_wheel)
+
+            if universal_python2_wheel:
+                return self._get_info_from_wheel(universal_python2_wheel)
+
+            if platform_specific_wheels:
+                first_wheel = platform_specific_wheels[0]
+                return self._get_info_from_wheel(first_wheel)
+
+        return self._get_info_from_sdist(urls["sdist"][0])
+
+    def _links_to_data(self, links: list[Link], data: PackageInfo) -> dict[str, Any]:
+        if not links:
+            raise PackageNotFound(
+                f'No valid distribution links found for package: "{data.name}" version:'
+                f' "{data.version}"'
+            )
+        urls = defaultdict(list)
+        files: list[dict[str, Any]] = []
+        for link in links:
+            if link.yanked and not data.yanked:
+                # drop yanked files unless the entire release is yanked
+                continue
+            if link.is_wheel:
+                urls["bdist_wheel"].append(link.url)
+            elif link.filename.endswith(
+                (".tar.gz", ".zip", ".bz2", ".xz", ".Z", ".tar")
+            ):
+                urls["sdist"].append(link.url)
+
+            file_hash = f"{link.hash_name}:{link.hash}" if link.hash else None
+
+            if not link.hash or (
+                link.hash_name is not None
+                and link.hash_name not in ("sha256", "sha384", "sha512")
+                and hasattr(hashlib, link.hash_name)
+            ):
+                with temporary_directory() as temp_dir:
+                    filepath = Path(temp_dir) / link.filename
+                    self._download(link.url, filepath)
+
+                    known_hash = (
+                        getattr(hashlib, link.hash_name)() if link.hash_name else None
+                    )
+                    required_hash = hashlib.sha256()
+
+                    chunksize = 4096
+                    with filepath.open("rb") as f:
+                        while True:
+                            chunk = f.read(chunksize)
+                            if not chunk:
+                                break
+                            if known_hash:
+                                known_hash.update(chunk)
+                            required_hash.update(chunk)
+
+                    if not known_hash or known_hash.hexdigest() == link.hash:
+                        file_hash = f"{required_hash.name}:{required_hash.hexdigest()}"
+
+            files.append({"file": link.filename, "hash": file_hash})
+
+        data.files = files
+
+        info = self._get_info_from_urls(urls)
+
+        data.summary = info.summary
+        data.requires_dist = info.requires_dist
+        data.requires_python = info.requires_python
+
+        return data.asdict()
+
+    def _get_response(self, endpoint: str) -> requests.Response | None:
+        url = self._url + endpoint
+        try:
+            response: requests.Response = self.session.get(
+                url, raise_for_status=False, timeout=REQUESTS_TIMEOUT
+            )
+            if response.status_code in (401, 403):
+                self._log(
+                    f"Authorization error accessing {url}",
+                    level="warning",
+                )
+                return None
+            if response.status_code == 404:
+                return None
+            response.raise_for_status()
+        except requests.exceptions.HTTPError as e:
+            raise RepositoryError(e)
+
+        if response.url != url:
+            self._log(
+                f"Response URL {response.url} differs from request URL {url}",
+                level="debug",
+            )
+        return response
+
+    def _get_page(self, name: NormalizedName) -> LinkSource:
+        response = self._get_response(f"/{name}/")
+        if not response:
+            raise PackageNotFound(f"Package [{name}] not found.")
+        return HTMLPage(response.url, response.text)
diff --git a/conda_lock/_vendor/poetry/repositories/installed_repository.py b/conda_lock/_vendor/poetry/repositories/installed_repository.py
index 1f1ab237a..ef7701eeb 100644
--- a/conda_lock/_vendor/poetry/repositories/installed_repository.py
+++ b/conda_lock/_vendor/poetry/repositories/installed_repository.py
@@ -1,15 +1,23 @@
+from __future__ import annotations
+
 import itertools
+import json
+import logging
 
-from typing import Set
-from typing import Union
+from pathlib import Path
+from typing import TYPE_CHECKING
 
-from conda_lock._vendor.poetry.core.packages import Package
+from packaging.utils import canonicalize_name
+from conda_lock._vendor.poetry.core.packages.package import Package
+from conda_lock._vendor.poetry.core.packages.utils.utils import url_to_path
 from conda_lock._vendor.poetry.core.utils.helpers import module_name
-from conda_lock._vendor.poetry.utils._compat import Path
+
+from conda_lock._vendor.poetry.repositories.repository import Repository
 from conda_lock._vendor.poetry.utils._compat import metadata
-from conda_lock._vendor.poetry.utils.env import Env
 
-from .repository import Repository
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.utils.env import Env
 
 
 _VENDORS = Path(__file__).parent.parent.joinpath("_vendor")
@@ -21,9 +29,15 @@
     FileNotFoundError = OSError
 
 
+logger = logging.getLogger(__name__)
+
+
 class InstalledRepository(Repository):
+    def __init__(self) -> None:
+        super().__init__("poetry-installed")
+
     @classmethod
-    def get_package_paths(cls, env, name):  # type: (Env, str) -> Set[Path]
+    def get_package_paths(cls, env: Env, name: str) -> set[Path]:
         """
         Process a .pth file within the site-packages directories, and return any valid
         paths. We skip executable .pth files as there is no reliable means to do this
@@ -41,10 +55,11 @@ def get_package_paths(cls, env, name):  # type: (Env, str) -> Set[Path]
         paths = set()
 
         # we identify the candidate pth files to check, this is done so to handle cases
-        # where the pth file for foo-bar might have been installed as either foo-bar.pth or
-        # foo_bar.pth (expected) in either pure or platform lib directories.
+        # where the pth file for foo-bar might have been installed as either foo-bar.pth
+        # or foo_bar.pth (expected) in either pure or platform lib directories.
         candidates = itertools.product(
-            {env.purelib, env.platlib}, {name, module_name(name)},
+            {env.purelib, env.platlib},
+            {name, module_name(name)},
         )
 
         for lib, module in candidates:
@@ -58,35 +73,24 @@ def get_package_paths(cls, env, name):  # type: (Env, str) -> Set[Path]
                     if line and not line.startswith(("#", "import ", "import\t")):
                         path = Path(line)
                         if not path.is_absolute():
-                            try:
-                                path = lib.joinpath(path).resolve()
-                            except FileNotFoundError:
-                                # this is required to handle pathlib oddity on win32 python==3.5
-                                path = lib.joinpath(path)
+                            path = lib.joinpath(path).resolve()
                         paths.add(path)
-        return paths
-
-    @classmethod
-    def set_package_vcs_properties_from_path(
-        cls, src, package
-    ):  # type: (Path, Package) -> None
-        from conda_lock._vendor.poetry.core.vcs.git import Git
 
-        git = Git()
-        revision = git.rev_parse("HEAD", src).strip()
-        url = git.remote_url(src)
+        src_path = env.path / "src" / name
+        if not paths and src_path.exists():
+            paths.add(src_path)
 
-        package._source_type = "git"
-        package._source_url = url
-        package._source_reference = revision
+        return paths
 
     @classmethod
-    def set_package_vcs_properties(cls, package, env):  # type: (Package, Env) -> None
-        src = env.path / "src" / package.name
-        cls.set_package_vcs_properties_from_path(src, package)
+    def get_package_vcs_properties_from_path(cls, src: Path) -> tuple[str, str, str]:
+        from conda_lock._vendor.poetry.vcs.git import Git
+
+        info = Git.info(repo=src)
+        return "git", info.origin, info.revision
 
     @classmethod
-    def is_vcs_package(cls, package, env):  # type: (Union[Path, Package], Env) -> bool
+    def is_vcs_package(cls, package: Path | Package, env: Env) -> bool:
         # A VCS dependency should have been installed
         # in the src directory.
         src = env.path / "src"
@@ -101,24 +105,180 @@ def is_vcs_package(cls, package, env):  # type: (Union[Path, Package], Env) -> b
             return True
 
     @classmethod
-    def load(cls, env):  # type: (Env) -> InstalledRepository
+    def create_package_from_distribution(
+        cls, distribution: metadata.Distribution, env: Env
+    ) -> Package:
+        # We first check for a direct_url.json file to determine
+        # the type of package.
+        path = Path(str(distribution._path))  # type: ignore[attr-defined]
+
+        if (
+            path.name.endswith(".dist-info")
+            and path.joinpath("direct_url.json").exists()
+        ):
+            return cls.create_package_from_pep610(distribution)
+
+        is_standard_package = env.is_path_relative_to_lib(path)
+
+        source_type = None
+        source_url = None
+        source_reference = None
+        source_resolved_reference = None
+        source_subdirectory = None
+        if is_standard_package:
+            if path.name.endswith(".dist-info"):
+                paths = cls.get_package_paths(
+                    env=env, name=distribution.metadata["name"]
+                )
+                if paths:
+                    is_editable_package = False
+                    for src in paths:
+                        if cls.is_vcs_package(src, env):
+                            (
+                                source_type,
+                                source_url,
+                                source_reference,
+                            ) = cls.get_package_vcs_properties_from_path(src)
+                            break
+
+                        if not (
+                            is_editable_package or env.is_path_relative_to_lib(src)
+                        ):
+                            is_editable_package = True
+                    else:
+                        # TODO: handle multiple source directories?
+                        if is_editable_package:
+                            source_type = "directory"
+                            source_url = paths.pop().as_posix()
+        elif cls.is_vcs_package(path, env):
+            (
+                source_type,
+                source_url,
+                source_reference,
+            ) = cls.get_package_vcs_properties_from_path(
+                env.path / "src" / canonicalize_name(distribution.metadata["name"])
+            )
+        else:
+            # If not, it's a path dependency
+            source_type = "directory"
+            source_url = str(path.parent)
+
+        package = Package(
+            distribution.metadata["name"],
+            distribution.metadata["version"],
+            source_type=source_type,
+            source_url=source_url,
+            source_reference=source_reference,
+            source_resolved_reference=source_resolved_reference,
+            source_subdirectory=source_subdirectory,
+        )
+
+        package.description = distribution.metadata.get(  # type: ignore[attr-defined]
+            "summary",
+            "",
+        )
+
+        return package
+
+    @classmethod
+    def create_package_from_pep610(cls, distribution: metadata.Distribution) -> Package:
+        path = Path(str(distribution._path))  # type: ignore[attr-defined]
+        source_type = None
+        source_url = None
+        source_reference = None
+        source_resolved_reference = None
+        source_subdirectory = None
+        develop = False
+
+        url_reference = json.loads(
+            path.joinpath("direct_url.json").read_text(encoding="utf-8")
+        )
+        if "archive_info" in url_reference:
+            # File or URL distribution
+            if url_reference["url"].startswith("file:"):
+                # File distribution
+                source_type = "file"
+                source_url = url_to_path(url_reference["url"]).as_posix()
+            else:
+                # URL distribution
+                source_type = "url"
+                source_url = url_reference["url"]
+        elif "dir_info" in url_reference:
+            # Directory distribution
+            source_type = "directory"
+            source_url = url_to_path(url_reference["url"]).as_posix()
+            develop = url_reference["dir_info"].get("editable", False)
+        elif "vcs_info" in url_reference:
+            # VCS distribution
+            source_type = url_reference["vcs_info"]["vcs"]
+            source_url = url_reference["url"]
+            source_resolved_reference = url_reference["vcs_info"]["commit_id"]
+            source_reference = url_reference["vcs_info"].get(
+                "requested_revision", source_resolved_reference
+            )
+        source_subdirectory = url_reference.get("subdirectory")
+
+        package = Package(
+            distribution.metadata["name"],
+            distribution.metadata["version"],
+            source_type=source_type,
+            source_url=source_url,
+            source_reference=source_reference,
+            source_resolved_reference=source_resolved_reference,
+            source_subdirectory=source_subdirectory,
+            develop=develop,
+        )
+
+        package.description = distribution.metadata.get(  # type: ignore[attr-defined]
+            "summary",
+            "",
+        )
+
+        return package
+
+    @classmethod
+    def load(cls, env: Env, with_dependencies: bool = False) -> InstalledRepository:
         """
         Load installed packages.
         """
+        from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+
         repo = cls()
         seen = set()
+        skipped = set()
 
         for entry in reversed(env.sys_path):
+            if not entry.strip():
+                logger.debug(
+                    "Project environment contains an empty path in sys_path,"
+                    " ignoring."
+                )
+                continue
+
             for distribution in sorted(
-                metadata.distributions(path=[entry]), key=lambda d: str(d._path),
+                metadata.distributions(  # type: ignore[no-untyped-call]
+                    path=[entry],
+                ),
+                key=lambda d: str(d._path),  # type: ignore[attr-defined]
             ):
-                name = distribution.metadata["name"]
-                path = Path(str(distribution._path))
-                version = distribution.metadata["version"]
-                package = Package(name, version, version)
-                package.description = distribution.metadata.get("summary", "")
+                path = Path(str(distribution._path))  # type: ignore[attr-defined]
 
-                if package.name in seen:
+                if path in skipped:
+                    continue
+
+                try:
+                    name = canonicalize_name(distribution.metadata["name"])
+                except TypeError:
+                    logger.warning(
+                        "Project environment contains an invalid distribution"
+                        " (%s). Consider removing it manually or recreate the"
+                        " environment.",
+                        path,
+                    )
+                    skipped.add(path)
+                    continue
+
+                if name in seen:
                     continue
 
                 try:
@@ -128,39 +288,14 @@ def load(cls, env):  # type: (Env) -> InstalledRepository
                 else:
                     continue
 
-                seen.add(package.name)
+                package = cls.create_package_from_distribution(distribution, env)
 
-                repo.add_package(package)
+                if with_dependencies:
+                    for require in distribution.metadata.get_all("requires-dist", []):
+                        dep = Dependency.create_from_pep_508(require)
+                        package.add_dependency(dep)
 
-                is_standard_package = env.is_path_relative_to_lib(path)
-
-                if is_standard_package:
-                    if path.name.endswith(".dist-info"):
-                        paths = cls.get_package_paths(env=env, name=package.pretty_name)
-                        if paths:
-                            is_editable_package = False
-                            for src in paths:
-                                if cls.is_vcs_package(src, env):
-                                    cls.set_package_vcs_properties(package, env)
-                                    break
-
-                                if not (
-                                    is_editable_package
-                                    or env.is_path_relative_to_lib(src)
-                                ):
-                                    is_editable_package = True
-                            else:
-                                # TODO: handle multiple source directories?
-                                if is_editable_package:
-                                    package._source_type = "directory"
-                                    package._source_url = paths.pop().as_posix()
-                    continue
-
-                if cls.is_vcs_package(path, env):
-                    cls.set_package_vcs_properties(package, env)
-                else:
-                    # If not, it's a path dependency
-                    package._source_type = "directory"
-                    package._source_url = str(path.parent)
+                seen.add(package.name)
+                repo.add_package(package)
 
         return repo
diff --git a/conda_lock/_vendor/poetry/repositories/legacy_repository.py b/conda_lock/_vendor/poetry/repositories/legacy_repository.py
old mode 100755
new mode 100644
index 3e8b4dcca..d719bccaf
--- a/conda_lock/_vendor/poetry/repositories/legacy_repository.py
+++ b/conda_lock/_vendor/poetry/repositories/legacy_repository.py
@@ -1,309 +1,53 @@
-import cgi
-import re
-import warnings
+from __future__ import annotations
 
-from collections import defaultdict
-from typing import Generator
-from typing import Optional
-from typing import Union
+from typing import TYPE_CHECKING
+from typing import Any
 
-import requests
-import requests.auth
+from conda_lock._vendor.poetry.core.packages.package import Package
 
-from cachecontrol import CacheControl
-from cachecontrol.caches.file_cache import FileCache
-from cachy import CacheManager
+from conda_lock._vendor.poetry.inspection.info import PackageInfo
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
+from conda_lock._vendor.poetry.repositories.http_repository import HTTPRepository
+from conda_lock._vendor.poetry.repositories.link_sources.html import SimpleRepositoryPage
 
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.packages.utils.link import Link
-from conda_lock._vendor.poetry.core.semver import Version
-from conda_lock._vendor.poetry.core.semver import VersionConstraint
-from conda_lock._vendor.poetry.core.semver import VersionRange
-from conda_lock._vendor.poetry.core.semver import parse_constraint
-from conda_lock._vendor.poetry.locations import REPOSITORY_CACHE_DIR
-from conda_lock._vendor.poetry.utils._compat import Path
-from conda_lock._vendor.poetry.utils.helpers import canonicalize_name
-from conda_lock._vendor.poetry.utils.patterns import wheel_file_re
 
-from ..config.config import Config
-from ..inspection.info import PackageInfo
-from ..installation.authenticator import Authenticator
-from .exceptions import PackageNotFound
-from .exceptions import RepositoryError
-from .pypi_repository import PyPiRepository
+if TYPE_CHECKING:
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.constraints.version import Version
+    from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint
+    from conda_lock._vendor.poetry.core.packages.utils.link import Link
 
+    from conda_lock._vendor.poetry.config.config import Config
 
-try:
-    import urllib.parse as urlparse
-except ImportError:
-    import urlparse
 
-try:
-    from html import unescape
-except ImportError:
-    try:
-        from html.parser import HTMLParser
-    except ImportError:
-        from HTMLParser import HTMLParser
-
-    unescape = HTMLParser().unescape
-
-
-try:
-    from urllib.parse import quote
-except ImportError:
-    from urllib import quote
-
-
-with warnings.catch_warnings():
-    warnings.simplefilter("ignore")
-    import html5lib
-
-
-class Page:
-
-    VERSION_REGEX = re.compile(r"(?i)([a-z0-9_\-.]+?)-(?=\d)([a-z0-9_.!+-]+)")
-    SUPPORTED_FORMATS = [
-        ".tar.gz",
-        ".whl",
-        ".zip",
-        ".tar.bz2",
-        ".tar.xz",
-        ".tar.Z",
-        ".tar",
-    ]
-
-    def __init__(self, url, content, headers):
-        if not url.endswith("/"):
-            url += "/"
-
-        self._url = url
-        encoding = None
-        if headers and "Content-Type" in headers:
-            content_type, params = cgi.parse_header(headers["Content-Type"])
-
-            if "charset" in params:
-                encoding = params["charset"]
-
-        self._content = content
-
-        if encoding is None:
-            self._parsed = html5lib.parse(content, namespaceHTMLElements=False)
-        else:
-            self._parsed = html5lib.parse(
-                content, transport_encoding=encoding, namespaceHTMLElements=False
-            )
-
-    @property
-    def versions(self):  # type: () -> Generator[Version]
-        seen = set()
-        for link in self.links:
-            version = self.link_version(link)
-
-            if not version:
-                continue
-
-            if version in seen:
-                continue
-
-            seen.add(version)
-
-            yield version
-
-    @property
-    def links(self):  # type: () -> Generator[Link]
-        for anchor in self._parsed.findall(".//a"):
-            if anchor.get("href"):
-                href = anchor.get("href")
-                url = self.clean_link(urlparse.urljoin(self._url, href))
-                pyrequire = anchor.get("data-requires-python")
-                pyrequire = unescape(pyrequire) if pyrequire else None
-
-                link = Link(url, self, requires_python=pyrequire)
-
-                if link.ext not in self.SUPPORTED_FORMATS:
-                    continue
-
-                yield link
-
-    def links_for_version(self, version):  # type: (Version) -> Generator[Link]
-        for link in self.links:
-            if self.link_version(link) == version:
-                yield link
-
-    def link_version(self, link):  # type: (Link) -> Union[Version, None]
-        m = wheel_file_re.match(link.filename)
-        if m:
-            version = m.group("ver")
-        else:
-            info, ext = link.splitext()
-            match = self.VERSION_REGEX.match(info)
-            if not match:
-                return
-
-            version = match.group(2)
-
-        try:
-            version = Version.parse(version)
-        except ValueError:
-            return
-
-        return version
-
-    _clean_re = re.compile(r"[^a-z0-9$&+,/:;=?@.#%_\\|-]", re.I)
-
-    def clean_link(self, url):
-        """Makes sure a link is fully encoded.  That is, if a ' ' shows up in
-        the link, it will be rewritten to %20 (while not over-quoting
-        % or other characters)."""
-        return self._clean_re.sub(lambda match: "%%%2x" % ord(match.group(0)), url)
-
-
-class LegacyRepository(PyPiRepository):
+class LegacyRepository(HTTPRepository):
     def __init__(
-        self, name, url, config=None, disable_cache=False, cert=None, client_cert=None
-    ):  # type: (str, str, Optional[Config], bool, Optional[Path], Optional[Path]) -> None
+        self,
+        name: str,
+        url: str,
+        config: Config | None = None,
+        disable_cache: bool = False,
+    ) -> None:
         if name == "pypi":
             raise ValueError("The name [pypi] is reserved for repositories")
 
-        self._packages = []
-        self._name = name
-        self._url = url.rstrip("/")
-        self._client_cert = client_cert
-        self._cert = cert
-        self._cache_dir = REPOSITORY_CACHE_DIR / name
-        self._cache = CacheManager(
-            {
-                "default": "releases",
-                "serializer": "json",
-                "stores": {
-                    "releases": {"driver": "file", "path": str(self._cache_dir)},
-                    "packages": {"driver": "dict"},
-                    "matches": {"driver": "dict"},
-                },
-            }
-        )
-
-        self._authenticator = Authenticator(
-            config=config or Config(use_environment=True)
-        )
-        self._basic_auth = None
-        username, password = self._authenticator.get_credentials_for_url(self._url)
-        if username is not None and password is not None:
-            self._basic_auth = requests.auth.HTTPBasicAuth(username, password)
-
-        self._disable_cache = disable_cache
-
-    @property
-    def cert(self):  # type: () -> Optional[Path]
-        return self._cert
-
-    @property
-    def client_cert(self):  # type: () -> Optional[Path]
-        return self._client_cert
+        super().__init__(name, url.rstrip("/"), config, disable_cache)
 
     @property
-    def session(self):
-        session = self._authenticator.session
-
-        if self._basic_auth:
-            session.auth = self._basic_auth
-
-        if self._cert:
-            session.verify = str(self._cert)
-
-        if self._client_cert:
-            session.cert = str(self._client_cert)
-
-        return CacheControl(session, cache=FileCache(str(self._cache_dir / "_http")))
-
-    @property
-    def authenticated_url(self):  # type: () -> str
-        if not self._basic_auth:
-            return self.url
-
-        parsed = urlparse.urlparse(self.url)
-
-        return "{scheme}://{username}:{password}@{netloc}{path}".format(
-            scheme=parsed.scheme,
-            username=quote(self._basic_auth.username, safe=""),
-            password=quote(self._basic_auth.password, safe=""),
-            netloc=parsed.netloc,
-            path=parsed.path,
-        )
-
-    def find_packages(self, dependency):
-        packages = []
-
-        constraint = dependency.constraint
-        if constraint is None:
-            constraint = "*"
-
-        if not isinstance(constraint, VersionConstraint):
-            constraint = parse_constraint(constraint)
-
-        allow_prereleases = dependency.allows_prereleases()
-        if isinstance(constraint, VersionRange):
-            if (
-                constraint.max is not None
-                and constraint.max.is_prerelease()
-                or constraint.min is not None
-                and constraint.min.is_prerelease()
-            ):
-                allow_prereleases = True
-
-        key = dependency.name
-        if not constraint.is_any():
-            key = "{}:{}".format(key, str(constraint))
-
-        ignored_pre_release_versions = []
-
-        if self._cache.store("matches").has(key):
-            versions = self._cache.store("matches").get(key)
-        else:
-            page = self._get("/{}/".format(dependency.name.replace(".", "-")))
-            if page is None:
-                return []
-
-            versions = []
-            for version in page.versions:
-                if version.is_prerelease() and not allow_prereleases:
-                    if constraint.is_any():
-                        # we need this when all versions of the package are pre-releases
-                        ignored_pre_release_versions.append(version)
-                    continue
-
-                if constraint.allows(version):
-                    versions.append(version)
-
-            self._cache.store("matches").put(key, versions, 5)
-
-        for package_versions in (versions, ignored_pre_release_versions):
-            for version in package_versions:
-                package = Package(
-                    dependency.name,
-                    version,
-                    source_type="legacy",
-                    source_reference=self.name,
-                    source_url=self._url,
-                )
-
-                packages.append(package)
-
-            self._log(
-                "{} packages found for {} {}".format(
-                    len(packages), dependency.name, str(constraint)
-                ),
-                level="debug",
-            )
-
-            if packages or not constraint.is_any():
-                # we have matching packages, or constraint is not (*)
-                break
-
-        return packages
-
-    def package(self, name, version, extras=None):  # type: (...) -> Package
+    def packages(self) -> list[Package]:
+        # LegacyRepository._packages is not populated and other implementations
+        # implicitly rely on this (e.g. Pool.search via
+        # LegacyRepository.search). To avoid special-casing Pool or changing
+        # behavior, we stub and return an empty list.
+        #
+        # TODO: Rethinking search behaviour and design.
+        # Ref: https://github.com/python-poetry/poetry/issues/2446 and
+        # https://github.com/python-poetry/poetry/pull/6669#discussion_r990874908.
+        return []
+
+    def package(
+        self, name: str, version: Version, extras: list[str] | None = None
+    ) -> Package:
         """
         Retrieve the release information.
 
@@ -316,95 +60,80 @@ def package(self, name, version, extras=None):  # type: (...) -> Package
         should be much faster.
         """
         try:
-            index = self._packages.index(Package(name, version, version))
+            index = self._packages.index(Package(name, version))
 
             return self._packages[index]
         except ValueError:
-            package = super(LegacyRepository, self).package(name, version, extras)
+            package = super().package(name, version, extras)
             package._source_type = "legacy"
             package._source_url = self._url
             package._source_reference = self.name
 
             return package
 
-    def find_links_for_package(self, package):
-        page = self._get("/{}/".format(package.name.replace(".", "-")))
-        if page is None:
+    def find_links_for_package(self, package: Package) -> list[Link]:
+        try:
+            page = self.get_page(package.name)
+        except PackageNotFound:
             return []
 
-        return list(page.links_for_version(package.version))
-
-    def _get_release_info(self, name, version):  # type: (str, str) -> dict
-        page = self._get("/{}/".format(canonicalize_name(name).replace(".", "-")))
-        if page is None:
-            raise PackageNotFound('No package named "{}"'.format(name))
-
-        data = PackageInfo(
-            name=name,
-            version=version,
-            summary="",
-            platform=None,
-            requires_dist=[],
-            requires_python=None,
-            files=[],
-            cache_version=str(self.CACHE_VERSION),
-        )
-
-        links = list(page.links_for_version(Version.parse(version)))
-        if not links:
-            raise PackageNotFound(
-                'No valid distribution links found for package: "{}" version: "{}"'.format(
-                    name, version
-                )
-            )
-        urls = defaultdict(list)
-        files = []
-        for link in links:
-            if link.is_wheel:
-                urls["bdist_wheel"].append(link.url)
-            elif link.filename.endswith(
-                (".tar.gz", ".zip", ".bz2", ".xz", ".Z", ".tar")
-            ):
-                urls["sdist"].append(link.url)
-
-            h = link.hash
-            if h:
-                h = link.hash_name + ":" + link.hash
-                files.append({"file": link.filename, "hash": h})
+        return list(page.links_for_version(package.name, package.version))
 
-        data.files = files
-
-        info = self._get_info_from_urls(urls)
-
-        data.summary = info.summary
-        data.requires_dist = info.requires_dist
-        data.requires_python = info.requires_python
-
-        return data.asdict()
-
-    def _get(self, endpoint):  # type: (str) -> Union[Page, None]
-        url = self._url + endpoint
+    def _find_packages(
+        self, name: NormalizedName, constraint: VersionConstraint
+    ) -> list[Package]:
+        """
+        Find packages on the remote server.
+        """
         try:
-            response = self.session.get(url)
-            if response.status_code == 404:
-                return
-            response.raise_for_status()
-        except requests.HTTPError as e:
-            raise RepositoryError(e)
-
-        if response.status_code in (401, 403):
-            self._log(
-                "Authorization error accessing {url}".format(url=response.url),
-                level="warn",
-            )
-            return
+            page = self.get_page(name)
+        except PackageNotFound:
+            self._log(f"No packages found for {name}", level="debug")
+            return []
 
-        if response.url != url:
-            self._log(
-                "Response URL {response_url} differs from request URL {url}".format(
-                    response_url=response.url, url=url
-                ),
-                level="debug",
+        versions = [
+            (version, page.yanked(name, version))
+            for version in page.versions(name)
+            if constraint.allows(version)
+        ]
+
+        return [
+            Package(
+                name,
+                version,
+                source_type="legacy",
+                source_reference=self.name,
+                source_url=self._url,
+                yanked=yanked,
             )
+            for version, yanked in versions
+        ]
+
+    def _get_release_info(
+        self, name: NormalizedName, version: Version
+    ) -> dict[str, Any]:
+        page = self.get_page(name)
+
+        links = list(page.links_for_version(name, version))
+        yanked = page.yanked(name, version)
+
+        return self._links_to_data(
+            links,
+            PackageInfo(
+                name=name,
+                version=version.text,
+                summary="",
+                platform=None,
+                requires_dist=[],
+                requires_python=None,
+                files=[],
+                yanked=yanked,
+                cache_version=str(self.CACHE_VERSION),
+            ),
+        )
 
-        return Page(response.url, response.content, response.headers)
+    def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage:
+        response = self._get_response(f"/{name}/")
+        if not response:
+            raise PackageNotFound(f"Package [{name}] not found.")
+        return SimpleRepositoryPage(response.url, response.text)
diff --git a/conda_lock/_vendor/poetry/repositories/link_sources/__init__.py b/conda_lock/_vendor/poetry/repositories/link_sources/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conda_lock/_vendor/poetry/repositories/link_sources/base.py b/conda_lock/_vendor/poetry/repositories/link_sources/base.py
new file mode 100644
index 000000000..36a6a5ae6
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/link_sources/base.py
@@ -0,0 +1,125 @@
+from __future__ import annotations
+
+import logging
+import re
+
+from typing import TYPE_CHECKING
+from typing import DefaultDict
+from typing import List
+
+from conda_lock._vendor.poetry.core.constraints.version import Version
+from conda_lock._vendor.poetry.core.packages.package import Package
+
+from conda_lock._vendor.poetry.utils._compat import cached_property
+from conda_lock._vendor.poetry.utils.patterns import sdist_file_re
+from conda_lock._vendor.poetry.utils.patterns import wheel_file_re
+
+
+if TYPE_CHECKING:
+    from collections.abc import Iterator
+
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.packages.utils.link import Link
+
+    LinkCache = DefaultDict[NormalizedName, DefaultDict[Version, List[Link]]]
+
+
+logger = logging.getLogger(__name__)
+
+
+class LinkSource:
+    VERSION_REGEX = re.compile(r"(?i)([a-z0-9_\-.]+?)-(?=\d)([a-z0-9_.!+-]+)")
+    CLEAN_REGEX = re.compile(r"[^a-z0-9$&+,/:;=?@.#%_\\|-]", re.I)
+    SUPPORTED_FORMATS = [
+        ".tar.gz",
+        ".whl",
+        ".zip",
+        ".tar.bz2",
+        ".tar.xz",
+        ".tar.Z",
+        ".tar",
+    ]
+
+    def __init__(self, url: str) -> None:
+        self._url = url
+
+    @property
+    def url(self) -> str:
+        return self._url
+
+    def versions(self, name: NormalizedName) -> Iterator[Version]:
+        yield from self._link_cache[name]
+
+    @property
+    def packages(self) -> Iterator[Package]:
+        for link in self.links:
+            pkg = self.link_package_data(link)
+
+            if pkg:
+                yield pkg
+
+    @property
+    def links(self) -> Iterator[Link]:
+        for links_per_version in self._link_cache.values():
+            for links in links_per_version.values():
+                yield from links
+
+    @classmethod
+    def link_package_data(cls, link: Link) -> Package | None:
+        name: str | None = None
+        version_string: str | None = None
+        version: Version | None = None
+        m = wheel_file_re.match(link.filename) or sdist_file_re.match(link.filename)
+
+        if m:
+            name = m.group("name")
+            version_string = m.group("ver")
+        else:
+            info, ext = link.splitext()
+            match = cls.VERSION_REGEX.match(info)
+            if match:
+                name = match.group(1)
+                version_string = match.group(2)
+
+        if version_string:
+            try:
+                version = Version.parse(version_string)
+            except ValueError:
+                logger.debug(
+                    "Skipping url (%s) due to invalid version (%s)", link.url, version
+                )
+                return None
+
+        pkg = None
+        if name and version:
+            pkg = Package(name, version, source_url=link.url)
+        return pkg
+
+    def links_for_version(
+        self, name: NormalizedName, version: Version
+    ) -> Iterator[Link]:
+        yield from self._link_cache[name][version]
+
+    def clean_link(self, url: str) -> str:
+        """Makes sure a link is fully encoded.  That is, if a ' ' shows up in
+        the link, it will be rewritten to %20 (while not over-quoting
+        % or other characters)."""
+        return self.CLEAN_REGEX.sub(lambda match: f"%{ord(match.group(0)):02x}", url)
+
+    def yanked(self, name: NormalizedName, version: Version) -> str | bool:
+        reasons = set()
+        for link in self.links_for_version(name, version):
+            if link.yanked:
+                if link.yanked_reason:
+                    reasons.add(link.yanked_reason)
+            else:
+                # release is not yanked if at least one file is not yanked
+                return False
+        # if all files are yanked (or there are no files) the release is yanked
+        if reasons:
+            return "\n".join(sorted(reasons))
+        return True
+
+    @cached_property
+    def _link_cache(self) -> LinkCache:
+        raise NotImplementedError()
diff --git a/conda_lock/_vendor/poetry/repositories/link_sources/html.py b/conda_lock/_vendor/poetry/repositories/link_sources/html.py
new file mode 100644
index 000000000..ace918734
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/link_sources/html.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+import urllib.parse
+import warnings
+
+from collections import defaultdict
+from html import unescape
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.core.packages.utils.link import Link
+
+from conda_lock._vendor.poetry.repositories.link_sources.base import LinkSource
+from conda_lock._vendor.poetry.utils._compat import cached_property
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.repositories.link_sources.base import LinkCache
+
+
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore")
+    import html5lib
+
+
+class HTMLPage(LinkSource):
+    def __init__(self, url: str, content: str) -> None:
+        super().__init__(url=url)
+
+        self._parsed = html5lib.parse(content, namespaceHTMLElements=False)
+
+    @cached_property
+    def _link_cache(self) -> LinkCache:
+        links: LinkCache = defaultdict(lambda: defaultdict(list))
+        for anchor in self._parsed.findall(".//a"):
+            if anchor.get("href"):
+                href = anchor.get("href")
+                url = self.clean_link(urllib.parse.urljoin(self._url, href))
+                pyrequire = anchor.get("data-requires-python")
+                pyrequire = unescape(pyrequire) if pyrequire else None
+                yanked_value = anchor.get("data-yanked")
+                yanked: str | bool
+                if yanked_value:
+                    yanked = unescape(yanked_value)
+                else:
+                    yanked = "data-yanked" in anchor.attrib
+                link = Link(url, requires_python=pyrequire, yanked=yanked)
+
+                if link.ext not in self.SUPPORTED_FORMATS:
+                    continue
+
+                pkg = self.link_package_data(link)
+                if pkg:
+                    links[pkg.name][pkg.version].append(link)
+
+        return links
+
+
+class SimpleRepositoryPage(HTMLPage):
+    def __init__(self, url: str, content: str) -> None:
+        if not url.endswith("/"):
+            url += "/"
+        super().__init__(url=url, content=content)
diff --git a/conda_lock/_vendor/poetry/repositories/link_sources/json.py b/conda_lock/_vendor/poetry/repositories/link_sources/json.py
new file mode 100644
index 000000000..7917a4bde
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/link_sources/json.py
@@ -0,0 +1,40 @@
+from __future__ import annotations
+
+from collections import defaultdict
+from typing import TYPE_CHECKING
+from typing import Any
+
+from conda_lock._vendor.poetry.core.packages.utils.link import Link
+
+from conda_lock._vendor.poetry.repositories.link_sources.base import LinkSource
+from conda_lock._vendor.poetry.utils._compat import cached_property
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.repositories.link_sources.base import LinkCache
+
+
+class SimpleJsonPage(LinkSource):
+    """Links as returned by PEP 691 compatible JSON-based Simple API."""
+
+    def __init__(self, url: str, content: dict[str, Any]) -> None:
+        super().__init__(url=url)
+        self.content = content
+
+    @cached_property
+    def _link_cache(self) -> LinkCache:
+        links: LinkCache = defaultdict(lambda: defaultdict(list))
+        for file in self.content["files"]:
+            url = file["url"]
+            requires_python = file.get("requires-python")
+            yanked = file.get("yanked", False)
+            link = Link(url, requires_python=requires_python, yanked=yanked)
+
+            if link.ext not in self.SUPPORTED_FORMATS:
+                continue
+
+            pkg = self.link_package_data(link)
+            if pkg:
+                links[pkg.name][pkg.version].append(link)
+
+        return links
diff --git a/conda_lock/_vendor/poetry/repositories/lockfile_repository.py b/conda_lock/_vendor/poetry/repositories/lockfile_repository.py
new file mode 100644
index 000000000..bc1921696
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/lockfile_repository.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.repositories import Repository
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+
+class LockfileRepository(Repository):
+    """
+    Special repository that distinguishes packages not only by name and version,
+    but also by source type, url, etc.
+    """
+
+    def __init__(self) -> None:
+        super().__init__("poetry-lockfile")
+
+    def has_package(self, package: Package) -> bool:
+        return any(p == package for p in self.packages)
+
+    def remove_package(self, package: Package) -> None:
+        index = None
+        for i, repo_package in enumerate(self.packages):
+            if repo_package == package:
+                index = i
+                break
+
+        if index is not None:
+            del self._packages[index]
diff --git a/conda_lock/_vendor/poetry/repositories/pool.py b/conda_lock/_vendor/poetry/repositories/pool.py
old mode 100755
new mode 100644
index 6f5c64a19..18ac61a65
--- a/conda_lock/_vendor/poetry/repositories/pool.py
+++ b/conda_lock/_vendor/poetry/repositories/pool.py
@@ -1,184 +1,27 @@
+from __future__ import annotations
+
+import warnings
+
 from typing import TYPE_CHECKING
-from typing import Dict
-from typing import List
-from typing import Optional
 
-from .base_repository import BaseRepository
-from .exceptions import PackageNotFound
-from .repository import Repository
+from conda_lock._vendor.poetry.repositories.repository_pool import RepositoryPool
 
 
 if TYPE_CHECKING:
-    from conda_lock._vendor.poetry.core.packages import Package
+    from conda_lock._vendor.poetry.repositories.repository import Repository
 
 
-class Pool(BaseRepository):
+class Pool(RepositoryPool):
     def __init__(
-        self, repositories=None, ignore_repository_names=False
-    ):  # type: (Optional[List[Repository]], bool) -> None
-        if repositories is None:
-            repositories = []
-
-        self._lookup = {}  # type: Dict[str, int]
-        self._repositories = []  # type: List[Repository]
-        self._default = False
-        self._has_primary_repositories = False
-        self._secondary_start_idx = None
-
-        for repository in repositories:
-            self.add_repository(repository)
-
-        self._ignore_repository_names = ignore_repository_names
-
-        super(Pool, self).__init__()
-
-    @property
-    def repositories(self):  # type: () -> List[Repository]
-        return self._repositories
-
-    def has_default(self):  # type: () -> bool
-        return self._default
-
-    def has_primary_repositories(self):  # type: () -> bool
-        return self._has_primary_repositories
-
-    def has_repository(self, name):  # type: (str) -> bool
-        name = name.lower() if name is not None else None
-
-        return name in self._lookup
-
-    def repository(self, name):  # type: (str) -> Repository
-        if name is not None:
-            name = name.lower()
-
-        if name in self._lookup:
-            return self._repositories[self._lookup[name]]
-
-        raise ValueError('Repository "{}" does not exist.'.format(name))
-
-    def add_repository(
-        self, repository, default=False, secondary=False
-    ):  # type: (Repository, bool, bool) -> Pool
-        """
-        Adds a repository to the pool.
-        """
-        repository_name = (
-            repository.name.lower() if repository.name is not None else None
+        self,
+        repositories: list[Repository] | None = None,
+        ignore_repository_names: bool = False,
+    ) -> None:
+        warnings.warn(
+            "Object Pool from poetry.repositories.pool is renamed and scheduled for"
+            " removal in poetry release 1.4.0. Please migrate to RepositoryPool from"
+            " poetry.repositories.repository_pool.",
+            DeprecationWarning,
+            stacklevel=2,
         )
-        if default:
-            if self.has_default():
-                raise ValueError("Only one repository can be the default")
-
-            self._default = True
-            self._repositories.insert(0, repository)
-            for name in self._lookup:
-                self._lookup[name] += 1
-
-            if self._secondary_start_idx is not None:
-                self._secondary_start_idx += 1
-
-            self._lookup[repository_name] = 0
-        elif secondary:
-            if self._secondary_start_idx is None:
-                self._secondary_start_idx = len(self._repositories)
-
-            self._repositories.append(repository)
-            self._lookup[repository_name] = len(self._repositories) - 1
-        else:
-            self._has_primary_repositories = True
-            if self._secondary_start_idx is None:
-                self._repositories.append(repository)
-                self._lookup[repository_name] = len(self._repositories) - 1
-            else:
-                self._repositories.insert(self._secondary_start_idx, repository)
-
-                for name, idx in self._lookup.items():
-                    if idx < self._secondary_start_idx:
-                        continue
-
-                    self._lookup[name] += 1
-
-                self._lookup[repository_name] = self._secondary_start_idx
-                self._secondary_start_idx += 1
-
-        return self
-
-    def remove_repository(self, repository_name):  # type: (str) -> Pool
-        if repository_name is not None:
-            repository_name = repository_name.lower()
-
-        idx = self._lookup.get(repository_name)
-        if idx is not None:
-            del self._repositories[idx]
-
-        return self
-
-    def has_package(self, package):
-        raise NotImplementedError()
-
-    def package(
-        self, name, version, extras=None, repository=None
-    ):  # type: (str, str, List[str], str) -> Package
-        if repository is not None:
-            repository = repository.lower()
-
-        if (
-            repository is not None
-            and repository not in self._lookup
-            and not self._ignore_repository_names
-        ):
-            raise ValueError('Repository "{}" does not exist.'.format(repository))
-
-        if repository is not None and not self._ignore_repository_names:
-            try:
-                return self.repository(repository).package(name, version, extras=extras)
-            except PackageNotFound:
-                pass
-        else:
-            for idx, repo in enumerate(self._repositories):
-                try:
-                    package = repo.package(name, version, extras=extras)
-                except PackageNotFound:
-                    continue
-
-                if package:
-                    self._packages.append(package)
-
-                    return package
-
-        raise PackageNotFound("Package {} ({}) not found.".format(name, version))
-
-    def find_packages(
-        self, dependency,
-    ):
-        repository = dependency.source_name
-        if repository is not None:
-            repository = repository.lower()
-
-        if (
-            repository is not None
-            and repository not in self._lookup
-            and not self._ignore_repository_names
-        ):
-            raise ValueError('Repository "{}" does not exist.'.format(repository))
-
-        if repository is not None and not self._ignore_repository_names:
-            return self.repository(repository).find_packages(dependency)
-
-        packages = []
-        for repo in self._repositories:
-            packages += repo.find_packages(dependency)
-
-        return packages
-
-    def search(self, query):
-        from .legacy_repository import LegacyRepository
-
-        results = []
-        for repository in self._repositories:
-            if isinstance(repository, LegacyRepository):
-                continue
-
-            results += repository.search(query)
-
-        return results
+        super().__init__(repositories, ignore_repository_names)
diff --git a/conda_lock/_vendor/poetry/repositories/pypi_repository.py b/conda_lock/_vendor/poetry/repositories/pypi_repository.py
old mode 100755
new mode 100644
index 2eb42b472..54b0d91de
--- a/conda_lock/_vendor/poetry/repositories/pypi_repository.py
+++ b/conda_lock/_vendor/poetry/repositories/pypi_repository.py
@@ -1,260 +1,158 @@
+from __future__ import annotations
+
 import logging
-import os
 
 from collections import defaultdict
-from typing import Dict
-from typing import List
-from typing import Union
+from typing import TYPE_CHECKING
+from typing import Any
 
 import requests
 
-from cachecontrol import CacheControl
-from cachecontrol.caches.file_cache import FileCache
 from cachecontrol.controller import logger as cache_control_logger
-from cachy import CacheManager
 from html5lib.html5parser import parse
-
-from conda_lock._vendor.poetry.core.packages import Dependency
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.packages import dependency_from_pep_508
+from conda_lock._vendor.poetry.core.packages.package import Package
 from conda_lock._vendor.poetry.core.packages.utils.link import Link
-from conda_lock._vendor.poetry.core.semver import VersionConstraint
-from conda_lock._vendor.poetry.core.semver import VersionRange
-from conda_lock._vendor.poetry.core.semver import parse_constraint
-from conda_lock._vendor.poetry.core.semver.exceptions import ParseVersionError
-from conda_lock._vendor.poetry.core.version.markers import parse_marker
-from conda_lock._vendor.poetry.locations import REPOSITORY_CACHE_DIR
-from conda_lock._vendor.poetry.utils._compat import Path
-from conda_lock._vendor.poetry.utils._compat import to_str
-from conda_lock._vendor.poetry.utils.helpers import download_file
-from conda_lock._vendor.poetry.utils.helpers import temporary_directory
-from conda_lock._vendor.poetry.utils.patterns import wheel_file_re
+from conda_lock._vendor.poetry.core.version.exceptions import InvalidVersion
 
-from ..inspection.info import PackageInfo
-from .exceptions import PackageNotFound
-from .remote_repository import RemoteRepository
-
-
-try:
-    import urllib.parse as urlparse
-except ImportError:
-    import urlparse
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
+from conda_lock._vendor.poetry.repositories.http_repository import HTTPRepository
+from conda_lock._vendor.poetry.repositories.link_sources.json import SimpleJsonPage
+from conda_lock._vendor.poetry.utils._compat import to_str
+from conda_lock._vendor.poetry.utils.constants import REQUESTS_TIMEOUT
 
 
 cache_control_logger.setLevel(logging.ERROR)
 
 logger = logging.getLogger(__name__)
 
+if TYPE_CHECKING:
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.constraints.version import Version
+    from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint
 
-class PyPiRepository(RemoteRepository):
+SUPPORTED_PACKAGE_TYPES = {"sdist", "bdist_wheel"}
 
-    CACHE_VERSION = parse_constraint("1.0.0")
 
-    def __init__(self, url="https://pypi.org/", disable_cache=False, fallback=True):
-        super(PyPiRepository, self).__init__(url.rstrip("/") + "/simple/")
+class PyPiRepository(HTTPRepository):
+    def __init__(
+        self,
+        url: str = "https://pypi.org/",
+        disable_cache: bool = False,
+        fallback: bool = True,
+    ) -> None:
+        super().__init__(
+            "PyPI", url.rstrip("/") + "/simple/", disable_cache=disable_cache
+        )
 
         self._base_url = url
-        self._disable_cache = disable_cache
         self._fallback = fallback
 
-        release_cache_dir = REPOSITORY_CACHE_DIR / "pypi"
-        self._cache = CacheManager(
-            {
-                "default": "releases",
-                "serializer": "json",
-                "stores": {
-                    "releases": {"driver": "file", "path": str(release_cache_dir)},
-                    "packages": {"driver": "dict"},
-                },
-            }
-        )
-
-        self._cache_control_cache = FileCache(str(release_cache_dir / "_http"))
-        self._name = "PyPI"
-
-    @property
-    def session(self):
-        return CacheControl(requests.session(), cache=self._cache_control_cache)
-
-    def find_packages(self, dependency):  # type: (Dependency) -> List[Package]
-        """
-        Find packages on the remote server.
-        """
-        constraint = dependency.constraint
-        if constraint is None:
-            constraint = "*"
-
-        if not isinstance(constraint, VersionConstraint):
-            constraint = parse_constraint(constraint)
-
-        allow_prereleases = dependency.allows_prereleases()
-        if isinstance(constraint, VersionRange):
-            if (
-                constraint.max is not None
-                and constraint.max.is_prerelease()
-                or constraint.min is not None
-                and constraint.min.is_prerelease()
-            ):
-                allow_prereleases = True
-
-        try:
-            info = self.get_package_info(dependency.name)
-        except PackageNotFound:
-            self._log(
-                "No packages found for {} {}".format(dependency.name, str(constraint)),
-                level="debug",
-            )
-            return []
-
-        packages = []
-        ignored_pre_release_packages = []
-
-        for version, release in info["releases"].items():
-            if not release:
-                # Bad release
-                self._log(
-                    "No release information found for {}-{}, skipping".format(
-                        dependency.name, version
-                    ),
-                    level="debug",
-                )
-                continue
-
-            try:
-                package = Package(info["info"]["name"], version)
-            except ParseVersionError:
-                self._log(
-                    'Unable to parse version "{}" for the {} package, skipping'.format(
-                        version, dependency.name
-                    ),
-                    level="debug",
-                )
-                continue
-
-            if package.is_prerelease() and not allow_prereleases:
-                if constraint.is_any():
-                    # we need this when all versions of the package are pre-releases
-                    ignored_pre_release_packages.append(package)
-                continue
-
-            if not constraint or (constraint and constraint.allows(package.version)):
-                packages.append(package)
-
-        self._log(
-            "{} packages found for {} {}".format(
-                len(packages), dependency.name, str(constraint)
-            ),
-            level="debug",
-        )
-
-        return packages or ignored_pre_release_packages
-
-    def package(
-        self,
-        name,  # type: str
-        version,  # type: str
-        extras=None,  # type: (Union[list, None])
-    ):  # type: (...) -> Package
-        return self.get_release_info(name, version).to_package(name=name, extras=extras)
-
-    def search(self, query):
+    def search(self, query: str) -> list[Package]:
         results = []
 
         search = {"q": query}
 
-        response = requests.session().get(self._base_url + "search", params=search)
+        response = requests.session().get(
+            self._base_url + "search", params=search, timeout=REQUESTS_TIMEOUT
+        )
         content = parse(response.content, namespaceHTMLElements=False)
         for result in content.findall(".//*[@class='package-snippet']"):
-            name = result.find("h3/*[@class='package-snippet__name']").text
-            version = result.find("h3/*[@class='package-snippet__version']").text
+            name_element = result.find("h3/*[@class='package-snippet__name']")
+            version_element = result.find("h3/*[@class='package-snippet__version']")
 
-            if not name or not version:
+            if (
+                name_element is None
+                or version_element is None
+                or not name_element.text
+                or not version_element.text
+            ):
                 continue
 
-            description = result.find("p[@class='package-snippet__description']").text
-            if not description:
-                description = ""
+            name = name_element.text
+            version = version_element.text
+
+            description_element = result.find(
+                "p[@class='package-snippet__description']"
+            )
+            description = (
+                description_element.text
+                if description_element is not None and description_element.text
+                else ""
+            )
 
             try:
-                result = Package(name, version, description)
-                result.description = to_str(description.strip())
-                results.append(result)
-            except ParseVersionError:
+                package = Package(name, version)
+                package.description = to_str(description.strip())
+                results.append(package)
+            except InvalidVersion:
                 self._log(
-                    'Unable to parse version "{}" for the {} package, skipping'.format(
-                        version, name
-                    ),
+                    f'Unable to parse version "{version}" for the {name} package,'
+                    " skipping",
                     level="debug",
                 )
 
         return results
 
-    def get_package_info(self, name):  # type: (str) -> dict
+    def get_package_info(self, name: NormalizedName) -> dict[str, Any]:
         """
         Return the package information given its name.
 
         The information is returned from the cache if it exists
         or retrieved from the remote server.
         """
-        if self._disable_cache:
-            return self._get_package_info(name)
+        return self._get_package_info(name)
 
-        return self._cache.store("packages").remember_forever(
-            name, lambda: self._get_package_info(name)
-        )
-
-    def _get_package_info(self, name):  # type: (str) -> dict
-        data = self._get("pypi/{}/json".format(name))
-        if data is None:
-            raise PackageNotFound("Package [{}] not found.".format(name))
-
-        return data
-
-    def get_release_info(self, name, version):  # type: (str, str) -> PackageInfo
+    def _find_packages(
+        self, name: NormalizedName, constraint: VersionConstraint
+    ) -> list[Package]:
         """
-        Return the release information given a package name and a version.
-
-        The information is returned from the cache if it exists
-        or retrieved from the remote server.
+        Find packages on the remote server.
         """
-        if self._disable_cache:
-            return PackageInfo.load(self._get_release_info(name, version))
+        try:
+            json_page = self.get_page(name)
+        except PackageNotFound:
+            self._log(f"No packages found for {name}", level="debug")
+            return []
 
-        cached = self._cache.remember_forever(
-            "{}:{}".format(name, version), lambda: self._get_release_info(name, version)
-        )
+        versions = [
+            (version, json_page.yanked(name, version))
+            for version in json_page.versions(name)
+            if constraint.allows(version)
+        ]
 
-        cache_version = cached.get("_cache_version", "0.0.0")
-        if parse_constraint(cache_version) != self.CACHE_VERSION:
-            # The cache must be updated
-            self._log(
-                "The cache for {} {} is outdated. Refreshing.".format(name, version),
-                level="debug",
-            )
-            cached = self._get_release_info(name, version)
+        return [Package(name, version, yanked=yanked) for version, yanked in versions]
 
-            self._cache.forever("{}:{}".format(name, version), cached)
+    def _get_package_info(self, name: str) -> dict[str, Any]:
+        headers = {"Accept": "application/vnd.pypi.simple.v1+json"}
+        info = self._get(f"simple/{name}/", headers=headers)
+        if info is None:
+            raise PackageNotFound(f"Package [{name}] not found.")
 
-        return PackageInfo.load(cached)
+        return info
 
-    def find_links_for_package(self, package):
-        json_data = self._get("pypi/{}/{}/json".format(package.name, package.version))
+    def find_links_for_package(self, package: Package) -> list[Link]:
+        json_data = self._get(f"pypi/{package.name}/{package.version}/json")
         if json_data is None:
             return []
 
         links = []
         for url in json_data["urls"]:
-            h = "sha256={}".format(url["digests"]["sha256"])
-            links.append(Link(url["url"] + "#" + h))
+            if url["packagetype"] in SUPPORTED_PACKAGE_TYPES:
+                h = f"sha256={url['digests']['sha256']}"
+                links.append(Link(url["url"] + "#" + h, yanked=self._get_yanked(url)))
 
         return links
 
-    def _get_release_info(self, name, version):  # type: (str, str) -> dict
-        self._log("Getting info for {} ({}) from PyPI".format(name, version), "debug")
+    def _get_release_info(
+        self, name: NormalizedName, version: Version
+    ) -> dict[str, str | list[str] | None]:
+        from conda_lock._vendor.poetry.inspection.info import PackageInfo
+
+        self._log(f"Getting info for {name} ({version}) from PyPI", "debug")
 
-        json_data = self._get("pypi/{}/{}/json".format(name, version))
+        json_data = self._get(f"pypi/{name}/{version}/json")
         if json_data is None:
-            raise PackageNotFound("Package [{}] not found.".format(name))
+            raise PackageNotFound(f"Package [{name}] not found.")
 
         info = json_data["info"]
 
@@ -266,6 +164,7 @@ def _get_release_info(self, name, version):  # type: (str, str) -> dict
             requires_dist=info["requires_dist"],
             requires_python=info["requires_python"],
             files=info.get("files", []),
+            yanked=self._get_yanked(info),
             cache_version=str(self.CACHE_VERSION),
         )
 
@@ -275,12 +174,13 @@ def _get_release_info(self, name, version):  # type: (str, str) -> dict
             version_info = []
 
         for file_info in version_info:
-            data.files.append(
-                {
-                    "file": file_info["filename"],
-                    "hash": "sha256:" + file_info["digests"]["sha256"],
-                }
-            )
+            if file_info["packagetype"] in SUPPORTED_PACKAGE_TYPES:
+                data.files.append(
+                    {
+                        "file": file_info["filename"],
+                        "hash": "sha256:" + file_info["digests"]["sha256"],
+                    }
+                )
 
         if self._fallback and data.requires_dist is None:
             self._log("No dependencies found, downloading archives", level="debug")
@@ -293,7 +193,7 @@ def _get_release_info(self, name, version):  # type: (str, str) -> dict
             for url in json_data["urls"]:
                 # Only get sdist and wheels if they exist
                 dist_type = url["packagetype"]
-                if dist_type not in ["sdist", "bdist_wheel"]:
+                if dist_type not in SUPPORTED_PACKAGE_TYPES:
                     continue
 
                 urls[dist_type].append(url["url"])
@@ -310,144 +210,40 @@ def _get_release_info(self, name, version):  # type: (str, str) -> dict
 
         return data.asdict()
 
-    def _get(self, endpoint):  # type: (str) -> Union[dict, None]
+    def _get_page(self, name: NormalizedName) -> SimpleJsonPage:
+        source = self._base_url + f"simple/{name}/"
+        info = self.get_package_info(name)
+        return SimpleJsonPage(source, info)
+
+    def _get(
+        self, endpoint: str, headers: dict[str, str] | None = None
+    ) -> dict[str, Any] | None:
         try:
-            json_response = self.session.get(self._base_url + endpoint)
+            json_response = self.session.get(
+                self._base_url + endpoint,
+                raise_for_status=False,
+                timeout=REQUESTS_TIMEOUT,
+                headers=headers,
+            )
         except requests.exceptions.TooManyRedirects:
             # Cache control redirect loop.
             # We try to remove the cache and try again
-            self._cache_control_cache.delete(self._base_url + endpoint)
-            json_response = self.session.get(self._base_url + endpoint)
+            self.session.delete_cache(self._base_url + endpoint)
+            json_response = self.session.get(
+                self._base_url + endpoint,
+                raise_for_status=False,
+                timeout=REQUESTS_TIMEOUT,
+                headers=headers,
+            )
 
-        if json_response.status_code == 404:
+        if json_response.status_code != 200:
             return None
 
-        json_data = json_response.json()
-
-        return json_data
-
-    def _get_info_from_urls(self, urls):  # type: (Dict[str, List[str]]) -> PackageInfo
-        # Checking wheels first as they are more likely to hold
-        # the necessary information
-        if "bdist_wheel" in urls:
-            # Check fo a universal wheel
-            wheels = urls["bdist_wheel"]
-
-            universal_wheel = None
-            universal_python2_wheel = None
-            universal_python3_wheel = None
-            platform_specific_wheels = []
-            for wheel in wheels:
-                link = Link(wheel)
-                m = wheel_file_re.match(link.filename)
-                if not m:
-                    continue
-
-                pyver = m.group("pyver")
-                abi = m.group("abi")
-                plat = m.group("plat")
-                if abi == "none" and plat == "any":
-                    # Universal wheel
-                    if pyver == "py2.py3":
-                        # Any Python
-                        universal_wheel = wheel
-                    elif pyver == "py2":
-                        universal_python2_wheel = wheel
-                    else:
-                        universal_python3_wheel = wheel
-                else:
-                    platform_specific_wheels.append(wheel)
-
-            if universal_wheel is not None:
-                return self._get_info_from_wheel(universal_wheel)
-
-            info = None
-            if universal_python2_wheel and universal_python3_wheel:
-                info = self._get_info_from_wheel(universal_python2_wheel)
-
-                py3_info = self._get_info_from_wheel(universal_python3_wheel)
-                if py3_info.requires_dist:
-                    if not info.requires_dist:
-                        info.requires_dist = py3_info.requires_dist
-
-                        return info
-
-                    py2_requires_dist = set(
-                        dependency_from_pep_508(r).to_pep_508()
-                        for r in info.requires_dist
-                    )
-                    py3_requires_dist = set(
-                        dependency_from_pep_508(r).to_pep_508()
-                        for r in py3_info.requires_dist
-                    )
-                    base_requires_dist = py2_requires_dist & py3_requires_dist
-                    py2_only_requires_dist = py2_requires_dist - py3_requires_dist
-                    py3_only_requires_dist = py3_requires_dist - py2_requires_dist
-
-                    # Normalizing requires_dist
-                    requires_dist = list(base_requires_dist)
-                    for requirement in py2_only_requires_dist:
-                        dep = dependency_from_pep_508(requirement)
-                        dep.marker = dep.marker.intersect(
-                            parse_marker("python_version == '2.7'")
-                        )
-                        requires_dist.append(dep.to_pep_508())
-
-                    for requirement in py3_only_requires_dist:
-                        dep = dependency_from_pep_508(requirement)
-                        dep.marker = dep.marker.intersect(
-                            parse_marker("python_version >= '3'")
-                        )
-                        requires_dist.append(dep.to_pep_508())
-
-                    info.requires_dist = sorted(list(set(requires_dist)))
-
-            if info:
-                return info
-
-            # Prefer non platform specific wheels
-            if universal_python3_wheel:
-                return self._get_info_from_wheel(universal_python3_wheel)
-
-            if universal_python2_wheel:
-                return self._get_info_from_wheel(universal_python2_wheel)
-
-            if platform_specific_wheels and "sdist" not in urls:
-                # Pick the first wheel available and hope for the best
-                return self._get_info_from_wheel(platform_specific_wheels[0])
-
-        return self._get_info_from_sdist(urls["sdist"][0])
-
-    def _get_info_from_wheel(self, url):  # type: (str) -> PackageInfo
-        self._log(
-            "Downloading wheel: {}".format(urlparse.urlparse(url).path.rsplit("/")[-1]),
-            level="debug",
-        )
-
-        filename = os.path.basename(urlparse.urlparse(url).path.rsplit("/")[-1])
-
-        with temporary_directory() as temp_dir:
-            filepath = Path(temp_dir) / filename
-            self._download(url, str(filepath))
-
-            return PackageInfo.from_wheel(filepath)
-
-    def _get_info_from_sdist(self, url):  # type: (str) -> PackageInfo
-        self._log(
-            "Downloading sdist: {}".format(urlparse.urlparse(url).path.rsplit("/")[-1]),
-            level="debug",
-        )
-
-        filename = os.path.basename(urlparse.urlparse(url).path)
-
-        with temporary_directory() as temp_dir:
-            filepath = Path(temp_dir) / filename
-            self._download(url, str(filepath))
-
-            return PackageInfo.from_sdist(filepath)
-
-    def _download(self, url, dest):  # type: (str, str) -> None
-        return download_file(url, dest, session=self.session)
+        json: dict[str, Any] = json_response.json()
+        return json
 
-    def _log(self, msg, level="info"):
-        getattr(logger, level)("{}: {}".format(self._name, msg))
+    @staticmethod
+    def _get_yanked(json_data: dict[str, Any]) -> str | bool:
+        if json_data.get("yanked", False):
+            return json_data.get("yanked_reason") or True  # noqa: SIM222
+        return False
diff --git a/conda_lock/_vendor/poetry/repositories/remote_repository.py b/conda_lock/_vendor/poetry/repositories/remote_repository.py
deleted file mode 100644
index 7717740d8..000000000
--- a/conda_lock/_vendor/poetry/repositories/remote_repository.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from .repository import Repository
-
-
-class RemoteRepository(Repository):
-    def __init__(self, url):  # type: (str) -> None
-        self._url = url
-
-        super(RemoteRepository, self).__init__()
-
-    @property
-    def url(self):  # type: () -> str
-        return self._url
-
-    @property
-    def authenticated_url(self):  # type: () -> str
-        return self._url
diff --git a/conda_lock/_vendor/poetry/repositories/repository.py b/conda_lock/_vendor/poetry/repositories/repository.py
old mode 100755
new mode 100644
index a8a8e555b..203719682
--- a/conda_lock/_vendor/poetry/repositories/repository.py
+++ b/conda_lock/_vendor/poetry/repositories/repository.py
@@ -1,89 +1,79 @@
-from conda_lock._vendor.poetry.core.semver import VersionConstraint
-from conda_lock._vendor.poetry.core.semver import VersionRange
-from conda_lock._vendor.poetry.core.semver import parse_constraint
+from __future__ import annotations
 
-from .base_repository import BaseRepository
+import logging
 
+from typing import TYPE_CHECKING
 
-class Repository(BaseRepository):
-    def __init__(self, packages=None, name=None):
-        super(Repository, self).__init__()
+from packaging.utils import canonicalize_name
+from conda_lock._vendor.poetry.core.constraints.version import Version
+from conda_lock._vendor.poetry.core.constraints.version import VersionRange
 
-        self._name = name
+from conda_lock._vendor.poetry.repositories.abstract_repository import AbstractRepository
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
 
-        if packages is None:
-            packages = []
 
-        for package in packages:
-            self.add_package(package)
+if TYPE_CHECKING:
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.constraints.version import VersionConstraint
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from conda_lock._vendor.poetry.core.packages.utils.link import Link
 
-    @property
-    def name(self):
-        return self._name
 
-    def package(self, name, version, extras=None):
-        name = name.lower()
+class Repository(AbstractRepository):
+    def __init__(self, name: str, packages: list[Package] | None = None) -> None:
+        super().__init__(name)
+        self._packages: list[Package] = []
 
-        for package in self.packages:
-            if name == package.name and package.version.text == version:
-                return package.clone()
+        for package in packages or []:
+            self.add_package(package)
 
-    def find_packages(self, dependency):
-        constraint = dependency.constraint
+    @property
+    def packages(self) -> list[Package]:
+        return self._packages
+
+    def find_packages(self, dependency: Dependency) -> list[Package]:
         packages = []
+        constraint, allow_prereleases = self._get_constraints_from_dependency(
+            dependency
+        )
         ignored_pre_release_packages = []
 
-        if constraint is None:
-            constraint = "*"
-
-        if not isinstance(constraint, VersionConstraint):
-            constraint = parse_constraint(constraint)
-
-        allow_prereleases = dependency.allows_prereleases()
-        if isinstance(constraint, VersionRange):
+        for package in self._find_packages(dependency.name, constraint):
+            if package.yanked and not isinstance(constraint, Version):
+                # PEP 592: yanked files are always ignored, unless they are the only
+                # file that matches a version specifier that "pins" to an exact
+                # version
+                continue
             if (
-                constraint.max is not None
-                and constraint.max.is_prerelease()
-                or constraint.min is not None
-                and constraint.min.is_prerelease()
+                package.is_prerelease()
+                and not allow_prereleases
+                and not package.is_direct_origin()
             ):
-                allow_prereleases = True
+                if constraint.is_any():
+                    # we need this when all versions of the package are pre-releases
+                    ignored_pre_release_packages.append(package)
+                continue
 
-        for package in self.packages:
-            if dependency.name == package.name:
-                if (
-                    package.is_prerelease()
-                    and not allow_prereleases
-                    and not package.source_type
-                ):
-                    # If prereleases are not allowed and the package is a prerelease
-                    # and is a standard package then we skip it
-                    if constraint.is_any():
-                        # we need this when all versions of the package are pre-releases
-                        ignored_pre_release_packages.append(package)
-                    continue
-
-                if constraint.allows(package.version) or (
-                    package.is_prerelease()
-                    and constraint.allows(package.version.next_patch)
-                ):
-                    packages.append(package)
+            packages.append(package)
+
+        self._log(
+            f"{len(packages)} packages found for {dependency.name} {constraint!s}",
+            level="debug",
+        )
 
         return packages or ignored_pre_release_packages
 
-    def has_package(self, package):
+    def has_package(self, package: Package) -> bool:
         package_id = package.unique_name
+        return any(
+            package_id == repo_package.unique_name for repo_package in self.packages
+        )
 
-        for repo_package in self.packages:
-            if package_id == repo_package.unique_name:
-                return True
-
-        return False
-
-    def add_package(self, package):
+    def add_package(self, package: Package) -> None:
         self._packages.append(package)
 
-    def remove_package(self, package):
+    def remove_package(self, package: Package) -> None:
         package_id = package.unique_name
 
         index = None
@@ -95,11 +85,8 @@ def remove_package(self, package):
         if index is not None:
             del self._packages[index]
 
-    def find_links_for_package(self, package):
-        return []
-
-    def search(self, query):
-        results = []
+    def search(self, query: str) -> list[Package]:
+        results: list[Package] = []
 
         for package in self.packages:
             if query in package.name:
@@ -107,5 +94,48 @@ def search(self, query):
 
         return results
 
-    def __len__(self):
+    @staticmethod
+    def _get_constraints_from_dependency(
+        dependency: Dependency,
+    ) -> tuple[VersionConstraint, bool]:
+        constraint = dependency.constraint
+
+        allow_prereleases = dependency.allows_prereleases()
+        if isinstance(constraint, VersionRange) and (
+            constraint.max is not None
+            and constraint.max.is_unstable()
+            or constraint.min is not None
+            and constraint.min.is_unstable()
+        ):
+            allow_prereleases = True
+
+        return constraint, allow_prereleases
+
+    def _find_packages(
+        self, name: NormalizedName, constraint: VersionConstraint
+    ) -> list[Package]:
+        return [
+            package
+            for package in self._packages
+            if package.name == name and constraint.allows(package.version)
+        ]
+
+    def _log(self, msg: str, level: str = "info") -> None:
+        logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
+        getattr(logger, level)(f"Source ({self.name}): {msg}")
+
+    def __len__(self) -> int:
         return len(self._packages)
+
+    def find_links_for_package(self, package: Package) -> list[Link]:
+        return []
+
+    def package(
+        self, name: str, version: Version, extras: list[str] | None = None
+    ) -> Package:
+        canonicalized_name = canonicalize_name(name)
+        for package in self.packages:
+            if canonicalized_name == package.name and package.version == version:
+                return package.clone()
+
+        raise PackageNotFound(f"Package {name} ({version}) not found.")
diff --git a/conda_lock/_vendor/poetry/repositories/repository_pool.py b/conda_lock/_vendor/poetry/repositories/repository_pool.py
new file mode 100644
index 000000000..03124a5f6
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/repository_pool.py
@@ -0,0 +1,143 @@
+from __future__ import annotations
+
+import enum
+
+from collections import OrderedDict
+from dataclasses import dataclass
+from enum import IntEnum
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.repositories.abstract_repository import AbstractRepository
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.constraints.version import Version
+    from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.repositories.repository import Repository
+
+
+class Priority(IntEnum):
+    # The order of the members below dictates the actual priority. The first member has
+    # top priority.
+    DEFAULT = enum.auto()
+    PRIMARY = enum.auto()
+    SECONDARY = enum.auto()
+
+
+@dataclass(frozen=True)
+class PrioritizedRepository:
+    repository: Repository
+    priority: Priority
+
+
+class RepositoryPool(AbstractRepository):
+    def __init__(
+        self,
+        repositories: list[Repository] | None = None,
+        ignore_repository_names: bool = False,
+    ) -> None:
+        super().__init__("poetry-repository-pool")
+        self._repositories: OrderedDict[str, PrioritizedRepository] = OrderedDict()
+        self._ignore_repository_names = ignore_repository_names
+
+        if repositories is None:
+            repositories = []
+        for repository in repositories:
+            self.add_repository(repository)
+
+    @property
+    def repositories(self) -> list[Repository]:
+        unsorted_repositories = self._repositories.values()
+        sorted_repositories = sorted(
+            unsorted_repositories, key=lambda prio_repo: prio_repo.priority
+        )
+        return [prio_repo.repository for prio_repo in sorted_repositories]
+
+    def has_default(self) -> bool:
+        return self._contains_priority(Priority.DEFAULT)
+
+    def has_primary_repositories(self) -> bool:
+        return self._contains_priority(Priority.PRIMARY)
+
+    def _contains_priority(self, priority: Priority) -> bool:
+        return any(
+            prio_repo.priority is priority for prio_repo in self._repositories.values()
+        )
+
+    def has_repository(self, name: str) -> bool:
+        return name.lower() in self._repositories
+
+    def repository(self, name: str) -> Repository:
+        name = name.lower()
+        if self.has_repository(name):
+            return self._repositories[name].repository
+        raise IndexError(f'Repository "{name}" does not exist.')
+
+    def add_repository(
+        self, repository: Repository, default: bool = False, secondary: bool = False
+    ) -> RepositoryPool:
+        """
+        Adds a repository to the pool.
+        """
+        repository_name = repository.name.lower()
+        if self.has_repository(repository_name):
+            raise ValueError(
+                f"A repository with name {repository_name} was already added."
+            )
+
+        if default and self.has_default():
+            raise ValueError("Only one repository can be the default.")
+
+        priority = Priority.PRIMARY
+        if default:
+            priority = Priority.DEFAULT
+        elif secondary:
+            priority = Priority.SECONDARY
+        self._repositories[repository_name] = PrioritizedRepository(
+            repository, priority
+        )
+        return self
+
+    def remove_repository(self, name: str) -> RepositoryPool:
+        if not self.has_repository(name):
+            raise IndexError(f"Pool can not remove unknown repository '{name}'.")
+        del self._repositories[name.lower()]
+        return self
+
+    def package(
+        self,
+        name: str,
+        version: Version,
+        extras: list[str] | None = None,
+        repository_name: str | None = None,
+    ) -> Package:
+        if repository_name and not self._ignore_repository_names:
+            return self.repository(repository_name).package(
+                name, version, extras=extras
+            )
+
+        for repo in self.repositories:
+            try:
+                return repo.package(name, version, extras=extras)
+            except PackageNotFound:
+                continue
+        raise PackageNotFound(f"Package {name} ({version}) not found.")
+
+    def find_packages(self, dependency: Dependency) -> list[Package]:
+        repository_name = dependency.source_name
+        if repository_name and not self._ignore_repository_names:
+            return self.repository(repository_name).find_packages(dependency)
+
+        packages: list[Package] = []
+        for repo in self.repositories:
+            packages += repo.find_packages(dependency)
+        return packages
+
+    def search(self, query: str) -> list[Package]:
+        results: list[Package] = []
+        for repository in self.repositories:
+            results += repository.search(query)
+        return results
diff --git a/conda_lock/_vendor/poetry/repositories/single_page_repository.py b/conda_lock/_vendor/poetry/repositories/single_page_repository.py
new file mode 100644
index 000000000..ce3cbae53
--- /dev/null
+++ b/conda_lock/_vendor/poetry/repositories/single_page_repository.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.repositories.exceptions import PackageNotFound
+from conda_lock._vendor.poetry.repositories.legacy_repository import LegacyRepository
+from conda_lock._vendor.poetry.repositories.link_sources.html import SimpleRepositoryPage
+
+
+if TYPE_CHECKING:
+    from packaging.utils import NormalizedName
+
+
+class SinglePageRepository(LegacyRepository):
+    def _get_page(self, name: NormalizedName) -> SimpleRepositoryPage:
+        """
+        Single page repositories only have one page irrespective of endpoint.
+        """
+        response = self._get_response("")
+        if not response:
+            raise PackageNotFound(f"Package [{name}] not found.")
+        return SimpleRepositoryPage(response.url, response.text)
diff --git a/conda_lock/_vendor/poetry/utils/_compat.py b/conda_lock/_vendor/poetry/utils/_compat.py
index 937f9b300..1a37ad13e 100644
--- a/conda_lock/_vendor/poetry/utils/_compat.py
+++ b/conda_lock/_vendor/poetry/utils/_compat.py
@@ -1,290 +1,79 @@
-import sys
-
-
-try:
-    from functools32 import lru_cache
-except ImportError:
-    from functools import lru_cache
-
-try:
-    from glob2 import glob
-except ImportError:
-    from glob import glob
-
-try:
-    import zipfile as zipp
-
-    from importlib import metadata
-except ImportError:
-    import importlib_metadata as metadata
-    import zipp
-
-try:
-    import urllib.parse as urlparse
-except ImportError:
-    import urlparse
-
-try:
-    from os import cpu_count
-except ImportError:  # Python 2
-    from multiprocessing import cpu_count
-
-try:  # Python 2
-    long = long
-    unicode = unicode
-    basestring = basestring
-except NameError:  # Python 3
-    long = int
-    unicode = str
-    basestring = str
+from __future__ import annotations
 
+import sys
 
-PY2 = sys.version_info[0] == 2
-PY34 = sys.version_info >= (3, 4)
-PY35 = sys.version_info >= (3, 5)
-PY36 = sys.version_info >= (3, 6)
+from contextlib import suppress
 
-WINDOWS = sys.platform == "win32"
 
-try:
-    from shlex import quote
-except ImportError:
-    # PY2
-    from pipes import quote  # noqa
+# TODO: use try/except ImportError when
+# https://github.com/python/mypy/issues/1393 is fixed
 
-if PY34:
-    from importlib.machinery import EXTENSION_SUFFIXES
+if sys.version_info < (3, 11):
+    # compatibility for python <3.11
+    import tomli as tomllib
 else:
-    from imp import get_suffixes
+    import tomllib  # nopycln: import
 
-    EXTENSION_SUFFIXES = [suffix[0] for suffix in get_suffixes()]
 
-
-if PY35:
-    from pathlib import Path
-else:
-    from pathlib2 import Path
-
-if not PY36:
-    from collections import OrderedDict
+if sys.version_info < (3, 10):
+    # compatibility for python <3.10
+    import importlib_metadata as metadata
 else:
-    OrderedDict = dict
-
-
-if PY35:
-    import subprocess as subprocess
+    from importlib import metadata
 
-    from subprocess import CalledProcessError
+if sys.version_info < (3, 8):
+    # compatibility for python <3.8
+    from backports.cached_property import cached_property
 else:
-    import subprocess32 as subprocess
-
-    from subprocess32 import CalledProcessError
-
-
-if PY34:
-    # subprocess32 pass the calls directly to subprocess
-    # on Python 3.3+ but Python 3.4 does not provide run()
-    # so we backport it
-    import signal
-
-    from subprocess import PIPE
-    from subprocess import Popen
-    from subprocess import SubprocessError
-    from subprocess import TimeoutExpired
-
-    class CalledProcessError(SubprocessError):
-        """Raised when run() is called with check=True and the process
-        returns a non-zero exit status.
-
-        Attributes:
-          cmd, returncode, stdout, stderr, output
-        """
-
-        def __init__(self, returncode, cmd, output=None, stderr=None):
-            self.returncode = returncode
-            self.cmd = cmd
-            self.output = output
-            self.stderr = stderr
-
-        def __str__(self):
-            if self.returncode and self.returncode < 0:
-                try:
-                    return "Command '%s' died with %r." % (
-                        self.cmd,
-                        signal.Signals(-self.returncode),
-                    )
-                except ValueError:
-                    return "Command '%s' died with unknown signal %d." % (
-                        self.cmd,
-                        -self.returncode,
-                    )
-            else:
-                return "Command '%s' returned non-zero exit status %d." % (
-                    self.cmd,
-                    self.returncode,
-                )
-
-        @property
-        def stdout(self):
-            """Alias for output attribute, to match stderr"""
-            return self.output
-
-        @stdout.setter
-        def stdout(self, value):
-            # There's no obvious reason to set this, but allow it anyway so
-            # .stdout is a transparent alias for .output
-            self.output = value
-
-    class CompletedProcess(object):
-        """A process that has finished running.
-        This is returned by run().
-        Attributes:
-          args: The list or str args passed to run().
-          returncode: The exit code of the process, negative for signals.
-          stdout: The standard output (None if not captured).
-          stderr: The standard error (None if not captured).
-        """
-
-        def __init__(self, args, returncode, stdout=None, stderr=None):
-            self.args = args
-            self.returncode = returncode
-            self.stdout = stdout
-            self.stderr = stderr
-
-        def __repr__(self):
-            args = [
-                "args={!r}".format(self.args),
-                "returncode={!r}".format(self.returncode),
-            ]
-            if self.stdout is not None:
-                args.append("stdout={!r}".format(self.stdout))
-            if self.stderr is not None:
-                args.append("stderr={!r}".format(self.stderr))
-            return "{}({})".format(type(self).__name__, ", ".join(args))
-
-        def check_returncode(self):
-            """Raise CalledProcessError if the exit code is non-zero."""
-            if self.returncode:
-                raise CalledProcessError(
-                    self.returncode, self.args, self.stdout, self.stderr
-                )
-
-    def run(*popenargs, **kwargs):
-        """Run command with arguments and return a CompletedProcess instance.
-        The returned instance will have attributes args, returncode, stdout and
-        stderr. By default, stdout and stderr are not captured, and those attributes
-        will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them.
-        If check is True and the exit code was non-zero, it raises a
-        CalledProcessError. The CalledProcessError object will have the return code
-        in the returncode attribute, and output & stderr attributes if those streams
-        were captured.
-        If timeout is given, and the process takes too long, a TimeoutExpired
-        exception will be raised.
-        There is an optional argument "input", allowing you to
-        pass a string to the subprocess's stdin.  If you use this argument
-        you may not also use the Popen constructor's "stdin" argument, as
-        it will be used internally.
-        The other arguments are the same as for the Popen constructor.
-        If universal_newlines=True is passed, the "input" argument must be a
-        string and stdout/stderr in the returned object will be strings rather than
-        bytes.
-        """
-        input = kwargs.pop("input", None)
-        timeout = kwargs.pop("timeout", None)
-        check = kwargs.pop("check", False)
-        if input is not None:
-            if "stdin" in kwargs:
-                raise ValueError("stdin and input arguments may not both be used.")
-            kwargs["stdin"] = PIPE
-
-        process = Popen(*popenargs, **kwargs)
-        try:
-            process.__enter__()  # No-Op really... illustrate "with in 2.4"
-            try:
-                stdout, stderr = process.communicate(input, timeout=timeout)
-            except TimeoutExpired:
-                process.kill()
-                stdout, stderr = process.communicate()
-                raise TimeoutExpired(
-                    process.args, timeout, output=stdout, stderr=stderr
-                )
-            except:
-                process.kill()
-                process.wait()
-                raise
-            retcode = process.poll()
-            if check and retcode:
-                raise CalledProcessError(
-                    retcode, process.args, output=stdout, stderr=stderr
-                )
-        finally:
-            # None because our context manager __exit__ does not use them.
-            process.__exit__(None, None, None)
-
-        return CompletedProcess(process.args, retcode, stdout, stderr)
-
-    subprocess.run = run
-    subprocess.CalledProcessError = CalledProcessError
+    from functools import cached_property
 
+WINDOWS = sys.platform == "win32"
 
-def decode(string, encodings=None):
-    if not PY2 and not isinstance(string, bytes):
-        return string
 
-    if PY2 and isinstance(string, unicode):
+def decode(string: bytes | str, encodings: list[str] | None = None) -> str:
+    if not isinstance(string, bytes):
         return string
 
     encodings = encodings or ["utf-8", "latin1", "ascii"]
 
     for encoding in encodings:
-        try:
+        with suppress(UnicodeEncodeError, UnicodeDecodeError):
             return string.decode(encoding)
-        except (UnicodeEncodeError, UnicodeDecodeError):
-            pass
 
     return string.decode(encodings[0], errors="ignore")
 
 
-def encode(string, encodings=None):
-    if not PY2 and isinstance(string, bytes):
-        return string
-
-    if PY2 and isinstance(string, str):
+def encode(string: str, encodings: list[str] | None = None) -> bytes:
+    if isinstance(string, bytes):
         return string
 
     encodings = encodings or ["utf-8", "latin1", "ascii"]
 
     for encoding in encodings:
-        try:
+        with suppress(UnicodeEncodeError, UnicodeDecodeError):
             return string.encode(encoding)
-        except (UnicodeEncodeError, UnicodeDecodeError):
-            pass
 
     return string.encode(encodings[0], errors="ignore")
 
 
-def to_str(string):
-    if isinstance(string, str) or not isinstance(string, (unicode, bytes)):
-        return string
-
-    if PY2:
-        method = "encode"
-    else:
-        method = "decode"
-
-    encodings = ["utf-8", "latin1", "ascii"]
-
-    for encoding in encodings:
-        try:
-            return getattr(string, method)(encoding)
-        except (UnicodeEncodeError, UnicodeDecodeError):
-            pass
-
-    return getattr(string, method)(encodings[0], errors="ignore")
+def to_str(string: str) -> str:
+    return decode(string)
 
 
-def list_to_shell_command(cmd):
+def list_to_shell_command(cmd: list[str]) -> str:
     return " ".join(
-        '"{}"'.format(token) if " " in token and token[0] not in {"'", '"'} else token
+        f'"{token}"' if " " in token and token[0] not in {"'", '"'} else token
         for token in cmd
     )
+
+
+__all__ = [
+    "WINDOWS",
+    "cached_property",
+    "decode",
+    "encode",
+    "list_to_shell_command",
+    "metadata",
+    "to_str",
+    "tomllib",
+]
diff --git a/conda_lock/_vendor/poetry/utils/appdirs.py b/conda_lock/_vendor/poetry/utils/appdirs.py
deleted file mode 100644
index 5b9da0cdd..000000000
--- a/conda_lock/_vendor/poetry/utils/appdirs.py
+++ /dev/null
@@ -1,252 +0,0 @@
-"""
-This code was taken from https://github.com/ActiveState/appdirs and modified
-to suit our purposes.
-"""
-import os
-import sys
-
-
-WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
-
-
-def expanduser(path):
-    """
-    Expand ~ and ~user constructions.
-
-    Includes a workaround for http://bugs.python.org/issue14768
-    """
-    expanded = os.path.expanduser(path)
-    if path.startswith("~/") and expanded.startswith("//"):
-        expanded = expanded[1:]
-    return expanded
-
-
-def user_cache_dir(appname):
-    r"""
-    Return full path to the user-specific cache dir for this application.
-
-        "appname" is the name of application.
-
-    Typical user cache directories are:
-        macOS:      ~/Library/Caches/
-        Unix:       ~/.cache/ (XDG default)
-        Windows:    C:\Users\\AppData\Local\\Cache
-
-    On Windows the only suggestion in the MSDN docs is that local settings go
-    in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the
-    non-roaming app data dir (the default returned by `user_data_dir`). Apps
-    typically put cache data somewhere *under* the given dir here. Some
-    examples:
-        ...\Mozilla\Firefox\Profiles\\Cache
-        ...\Acme\SuperApp\Cache\1.0
-
-    OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
-    """
-    if WINDOWS:
-        # Get the base path
-        path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
-
-        # Add our app name and Cache directory to it
-        path = os.path.join(path, appname, "Cache")
-    elif sys.platform == "darwin":
-        # Get the base path
-        path = expanduser("~/Library/Caches")
-
-        # Add our app name to it
-        path = os.path.join(path, appname)
-    else:
-        # Get the base path
-        path = os.getenv("XDG_CACHE_HOME", expanduser("~/.cache"))
-
-        # Add our app name to it
-        path = os.path.join(path, appname)
-
-    return path
-
-
-def user_data_dir(appname, roaming=False):
-    r"""
-    Return full path to the user-specific data dir for this application.
-
-        "appname" is the name of application.
-            If None, just the system directory is returned.
-        "roaming" (boolean, default False) can be set True to use the Windows
-            roaming appdata directory. That means that for users on a Windows
-            network setup for roaming profiles, this user data will be
-            sync'd on login. See
-            
-            for a discussion of issues.
-
-    Typical user data directories are:
-        macOS:                  ~/Library/Application Support/
-        Unix:                   ~/.local/share/    # or in
-                                $XDG_DATA_HOME, if defined
-        Win XP (not roaming):   C:\Documents and Settings\\ ...
-                                ...Application Data\
-        Win XP (roaming):       C:\Documents and Settings\\Local ...
-                                ...Settings\Application Data\
-        Win 7  (not roaming):   C:\Users\\AppData\Local\
-        Win 7  (roaming):       C:\Users\\AppData\Roaming\
-
-    For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
-    That means, by default "~/.local/share/".
-    """
-    if WINDOWS:
-        const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
-        path = os.path.join(os.path.normpath(_get_win_folder(const)), appname)
-    elif sys.platform == "darwin":
-        path = os.path.join(expanduser("~/Library/Application Support/"), appname)
-    else:
-        path = os.path.join(
-            os.getenv("XDG_DATA_HOME", expanduser("~/.local/share")), appname
-        )
-
-    return path
-
-
-def user_config_dir(appname, roaming=True):
-    """Return full path to the user-specific config dir for this application.
-
-        "appname" is the name of application.
-            If None, just the system directory is returned.
-        "roaming" (boolean, default True) can be set False to not use the
-            Windows roaming appdata directory. That means that for users on a
-            Windows network setup for roaming profiles, this user data will be
-            sync'd on login. See
-            
-            for a discussion of issues.
-
-    Typical user data directories are:
-        macOS:                  same as user_data_dir
-        Unix:                   ~/.config/
-        Win *:                  same as user_data_dir
-
-    For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
-    That means, by default "~/.config/".
-    """
-    if WINDOWS:
-        path = user_data_dir(appname, roaming=roaming)
-    elif sys.platform == "darwin":
-        path = user_data_dir(appname)
-    else:
-        path = os.getenv("XDG_CONFIG_HOME", expanduser("~/.config"))
-        path = os.path.join(path, appname)
-
-    return path
-
-
-# for the discussion regarding site_config_dirs locations
-# see 
-def site_config_dirs(appname):
-    r"""Return a list of potential user-shared config dirs for this application.
-
-        "appname" is the name of application.
-
-    Typical user config directories are:
-        macOS:      /Library/Application Support//
-        Unix:       /etc or $XDG_CONFIG_DIRS[i]// for each value in
-                    $XDG_CONFIG_DIRS
-        Win XP:     C:\Documents and Settings\All Users\Application ...
-                    ...Data\\
-        Vista:      (Fail! "C:\ProgramData" is a hidden *system* directory
-                    on Vista.)
-        Win 7:      Hidden, but writeable on Win 7:
-                    C:\ProgramData\\
-    """
-    if WINDOWS:
-        path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
-        pathlist = [os.path.join(path, appname)]
-    elif sys.platform == "darwin":
-        pathlist = [os.path.join("/Library/Application Support", appname)]
-    else:
-        # try looking in $XDG_CONFIG_DIRS
-        xdg_config_dirs = os.getenv("XDG_CONFIG_DIRS", "/etc/xdg")
-        if xdg_config_dirs:
-            pathlist = [
-                os.path.join(expanduser(x), appname)
-                for x in xdg_config_dirs.split(os.pathsep)
-            ]
-        else:
-            pathlist = []
-
-        # always look in /etc directly as well
-        pathlist.append("/etc")
-
-    return pathlist
-
-
-# -- Windows support functions --
-
-
-def _get_win_folder_from_registry(csidl_name):
-    """
-    This is a fallback technique at best. I'm not sure if using the
-    registry for this guarantees us the correct answer for all CSIDL_*
-    names.
-    """
-    import _winreg
-
-    shell_folder_name = {
-        "CSIDL_APPDATA": "AppData",
-        "CSIDL_COMMON_APPDATA": "Common AppData",
-        "CSIDL_LOCAL_APPDATA": "Local AppData",
-    }[csidl_name]
-
-    key = _winreg.OpenKey(
-        _winreg.HKEY_CURRENT_USER,
-        r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
-    )
-    directory, _type = _winreg.QueryValueEx(key, shell_folder_name)
-    return directory
-
-
-def _get_win_folder_with_ctypes(csidl_name):
-    csidl_const = {
-        "CSIDL_APPDATA": 26,
-        "CSIDL_COMMON_APPDATA": 35,
-        "CSIDL_LOCAL_APPDATA": 28,
-    }[csidl_name]
-
-    buf = ctypes.create_unicode_buffer(1024)
-    ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
-
-    # Downgrade to short path name if have highbit chars. See
-    # .
-    has_high_char = False
-    for c in buf:
-        if ord(c) > 255:
-            has_high_char = True
-            break
-    if has_high_char:
-        buf2 = ctypes.create_unicode_buffer(1024)
-        if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
-            buf = buf2
-
-    return buf.value
-
-
-if WINDOWS:
-    try:
-        import ctypes
-
-        _get_win_folder = _get_win_folder_with_ctypes
-    except ImportError:
-        _get_win_folder = _get_win_folder_from_registry
-
-
-def _win_path_to_bytes(path):
-    """Encode Windows paths to bytes. Only used on Python 2.
-
-    Motivation is to be consistent with other operating systems where paths
-    are also returned as bytes. This avoids problems mixing bytes and Unicode
-    elsewhere in the codebase. For more details and discussion see
-    .
-
-    If encoding using ASCII and MBCS fails, return the original Unicode path.
-    """
-    for encoding in ("ASCII", "MBCS"):
-        try:
-            return path.encode(encoding)
-        except (UnicodeEncodeError, LookupError):
-            pass
-    return path
diff --git a/conda_lock/_vendor/poetry/utils/authenticator.py b/conda_lock/_vendor/poetry/utils/authenticator.py
new file mode 100644
index 000000000..03da82926
--- /dev/null
+++ b/conda_lock/_vendor/poetry/utils/authenticator.py
@@ -0,0 +1,465 @@
+from __future__ import annotations
+
+import contextlib
+import dataclasses
+import functools
+import logging
+import time
+import urllib.parse
+
+from os.path import commonprefix
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Any
+
+import lockfile
+import requests
+import requests.auth
+import requests.exceptions
+
+from cachecontrol import CacheControlAdapter
+from cachecontrol.caches import FileCache
+from filelock import FileLock
+
+from conda_lock._vendor.poetry.config.config import Config
+from conda_lock._vendor.poetry.exceptions import PoetryException
+from conda_lock._vendor.poetry.utils.constants import REQUESTS_TIMEOUT
+from conda_lock._vendor.poetry.utils.password_manager import HTTPAuthCredential
+from conda_lock._vendor.poetry.utils.password_manager import PasswordManager
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.cleo.io.io import IO
+
+
+logger = logging.getLogger(__name__)
+
+
+class FileLockLockFile(lockfile.LockBase):  # type: ignore[misc]
+    # The default LockFile from the lockfile package as used by cachecontrol can remain
+    # locked if a process exits ungracefully.  See eg
+    # .
+    #
+    # FileLock from the filelock package does not have this problem, so we use that to
+    # construct something compatible with cachecontrol.
+    def __init__(
+        self, path: str, threaded: bool = True, timeout: float | None = None
+    ) -> None:
+        super().__init__(path, threaded, timeout)
+        self.file_lock = FileLock(self.lock_file)
+
+    def acquire(self, timeout: float | None = None) -> None:
+        self.file_lock.acquire(timeout=timeout)
+
+    def release(self) -> None:
+        self.file_lock.release()
+
+
+@dataclasses.dataclass(frozen=True)
+class RepositoryCertificateConfig:
+    cert: Path | None = dataclasses.field(default=None)
+    client_cert: Path | None = dataclasses.field(default=None)
+    verify: bool = dataclasses.field(default=True)
+
+    @classmethod
+    def create(
+        cls, repository: str, config: Config | None
+    ) -> RepositoryCertificateConfig:
+        config = config if config else Config.create()
+
+        verify: str | bool = config.get(
+            f"certificates.{repository}.verify",
+            config.get(f"certificates.{repository}.cert", True),
+        )
+        client_cert: str = config.get(f"certificates.{repository}.client-cert")
+
+        return cls(
+            cert=Path(verify) if isinstance(verify, str) else None,
+            client_cert=Path(client_cert) if client_cert else None,
+            verify=verify if isinstance(verify, bool) else True,
+        )
+
+
+@dataclasses.dataclass
+class AuthenticatorRepositoryConfig:
+    name: str
+    url: str
+    netloc: str = dataclasses.field(init=False)
+    path: str = dataclasses.field(init=False)
+
+    def __post_init__(self) -> None:
+        parsed_url = urllib.parse.urlsplit(self.url)
+        self.netloc = parsed_url.netloc
+        self.path = parsed_url.path
+
+    def certs(self, config: Config) -> RepositoryCertificateConfig:
+        return RepositoryCertificateConfig.create(self.name, config)
+
+    @property
+    def http_credential_keys(self) -> list[str]:
+        return [self.url, self.netloc, self.name]
+
+    def get_http_credentials(
+        self, password_manager: PasswordManager, username: str | None = None
+    ) -> HTTPAuthCredential:
+        # try with the repository name via the password manager
+        credential = HTTPAuthCredential(
+            **(password_manager.get_http_auth(self.name) or {})
+        )
+
+        if credential.password is None:
+            # fallback to url and netloc based keyring entries
+            credential = password_manager.keyring.get_credential(
+                self.url, self.netloc, username=credential.username
+            )
+
+            if credential.password is not None:
+                return HTTPAuthCredential(
+                    username=credential.username, password=credential.password
+                )
+
+        return credential
+
+
+class Authenticator:
+    def __init__(
+        self,
+        config: Config | None = None,
+        io: IO | None = None,
+        cache_id: str | None = None,
+        disable_cache: bool = False,
+        pool_size: int = 10,
+    ) -> None:
+        self._config = config or Config.create()
+        self._io = io
+        self._sessions_for_netloc: dict[str, requests.Session] = {}
+        self._credentials: dict[str, HTTPAuthCredential] = {}
+        self._certs: dict[str, RepositoryCertificateConfig] = {}
+        self._configured_repositories: dict[
+            str, AuthenticatorRepositoryConfig
+        ] | None = None
+        self._password_manager = PasswordManager(self._config)
+        self._cache_control = (
+            FileCache(
+                str(
+                    self._config.repository_cache_directory
+                    / (cache_id or "_default_cache")
+                    / "_http"
+                ),
+                lock_class=FileLockLockFile,
+            )
+            if not disable_cache
+            else None
+        )
+        self.get_repository_config_for_url = functools.lru_cache(maxsize=None)(
+            self._get_repository_config_for_url
+        )
+        self._pool_size = pool_size
+
+    def create_session(self) -> requests.Session:
+        session = requests.Session()
+
+        if self._cache_control is None:
+            return session
+
+        adapter = CacheControlAdapter(
+            cache=self._cache_control,
+            pool_maxsize=self._pool_size,
+        )
+        session.mount("http://", adapter)
+        session.mount("https://", adapter)
+
+        return session
+
+    def get_session(self, url: str | None = None) -> requests.Session:
+        if not url:
+            return self.create_session()
+
+        parsed_url = urllib.parse.urlsplit(url)
+        netloc = parsed_url.netloc
+
+        if netloc not in self._sessions_for_netloc:
+            logger.debug("Creating new session for %s", netloc)
+            self._sessions_for_netloc[netloc] = self.create_session()
+
+        return self._sessions_for_netloc[netloc]
+
+    def close(self) -> None:
+        for session in self._sessions_for_netloc.values():
+            if session is not None:
+                with contextlib.suppress(AttributeError):
+                    session.close()
+
+    def __del__(self) -> None:
+        self.close()
+
+    def delete_cache(self, url: str) -> None:
+        if self._cache_control is not None:
+            self._cache_control.delete(key=url)
+
+    def authenticated_url(self, url: str) -> str:
+        parsed = urllib.parse.urlparse(url)
+        credential = self.get_credentials_for_url(url)
+
+        if credential.username is not None and credential.password is not None:
+            username = urllib.parse.quote(credential.username, safe="")
+            password = urllib.parse.quote(credential.password, safe="")
+
+            return (
+                f"{parsed.scheme}://{username}:{password}@{parsed.netloc}{parsed.path}"
+            )
+
+        return url
+
+    def request(
+        self, method: str, url: str, raise_for_status: bool = True, **kwargs: Any
+    ) -> requests.Response:
+        headers = kwargs.get("headers")
+        request = requests.Request(method, url, headers=headers)
+        credential = self.get_credentials_for_url(url)
+
+        if credential.username is not None or credential.password is not None:
+            request = requests.auth.HTTPBasicAuth(
+                credential.username or "", credential.password or ""
+            )(request)
+
+        session = self.get_session(url=url)
+        prepared_request = session.prepare_request(request)
+
+        proxies: dict[str, str] = kwargs.get("proxies", {})
+        stream: bool | None = kwargs.get("stream")
+
+        certs = self.get_certs_for_url(url)
+        verify: bool | str | Path = kwargs.get("verify") or certs.cert or certs.verify
+        cert: str | Path | None = kwargs.get("cert") or certs.client_cert
+
+        if cert is not None:
+            cert = str(cert)
+
+        verify = str(verify) if isinstance(verify, Path) else verify
+
+        settings = session.merge_environment_settings(
+            prepared_request.url, proxies, stream, verify, cert
+        )
+
+        # Send the request.
+        send_kwargs = {
+            "timeout": kwargs.get("timeout", REQUESTS_TIMEOUT),
+            "allow_redirects": kwargs.get("allow_redirects", True),
+        }
+        send_kwargs.update(settings)
+
+        attempt = 0
+
+        while True:
+            is_last_attempt = attempt >= 5
+            try:
+                resp = session.send(prepared_request, **send_kwargs)
+            except (requests.exceptions.ConnectionError, OSError) as e:
+                if is_last_attempt:
+                    raise e
+            else:
+                if resp.status_code not in [502, 503, 504] or is_last_attempt:
+                    if raise_for_status:
+                        resp.raise_for_status()
+                    return resp
+
+            if not is_last_attempt:
+                attempt += 1
+                delay = 0.5 * attempt
+                logger.debug("Retrying HTTP request in %s seconds.", delay)
+                time.sleep(delay)
+                continue
+
+        # this should never really be hit under any sane circumstance
+        raise PoetryException("Failed HTTP {} request", method.upper())
+
+    def get(self, url: str, **kwargs: Any) -> requests.Response:
+        return self.request("get", url, **kwargs)
+
+    def post(self, url: str, **kwargs: Any) -> requests.Response:
+        return self.request("post", url, **kwargs)
+
+    def _get_credentials_for_repository(
+        self, repository: AuthenticatorRepositoryConfig, username: str | None = None
+    ) -> HTTPAuthCredential:
+        # cache repository credentials by repository url to avoid multiple keyring
+        # backend queries when packages are being downloaded from the same source
+        key = f"{repository.url}#username={username or ''}"
+
+        if key not in self._credentials:
+            self._credentials[key] = repository.get_http_credentials(
+                password_manager=self._password_manager, username=username
+            )
+
+        return self._credentials[key]
+
+    def _get_credentials_for_url(
+        self, url: str, exact_match: bool = False
+    ) -> HTTPAuthCredential:
+        repository = self.get_repository_config_for_url(url, exact_match)
+
+        credential = (
+            self._get_credentials_for_repository(repository=repository)
+            if repository is not None
+            else HTTPAuthCredential()
+        )
+
+        if credential.password is None:
+            parsed_url = urllib.parse.urlsplit(url)
+            netloc = parsed_url.netloc
+            credential = self._password_manager.keyring.get_credential(
+                url, netloc, username=credential.username
+            )
+
+            return HTTPAuthCredential(
+                username=credential.username, password=credential.password
+            )
+
+        return credential
+
+    def get_credentials_for_git_url(self, url: str) -> HTTPAuthCredential:
+        parsed_url = urllib.parse.urlsplit(url)
+
+        if parsed_url.scheme not in {"http", "https"}:
+            return HTTPAuthCredential()
+
+        key = f"git+{url}"
+
+        if key not in self._credentials:
+            self._credentials[key] = self._get_credentials_for_url(url, True)
+
+        return self._credentials[key]
+
+    def get_credentials_for_url(self, url: str) -> HTTPAuthCredential:
+        parsed_url = urllib.parse.urlsplit(url)
+        netloc = parsed_url.netloc
+
+        if url not in self._credentials:
+            if "@" not in netloc:
+                # no credentials were provided in the url, try finding the
+                # best repository configuration
+                self._credentials[url] = self._get_credentials_for_url(url)
+            else:
+                # Split from the right because that's how urllib.parse.urlsplit()
+                # behaves if more than one @ is present (which can be checked using
+                # the password attribute of urlsplit()'s return value).
+                auth, netloc = netloc.rsplit("@", 1)
+                # Split from the left because that's how urllib.parse.urlsplit()
+                # behaves if more than one : is present (which again can be checked
+                # using the password attribute of the return value)
+                user, password = auth.split(":", 1) if ":" in auth else (auth, "")
+                self._credentials[url] = HTTPAuthCredential(
+                    urllib.parse.unquote(user),
+                    urllib.parse.unquote(password),
+                )
+
+        return self._credentials[url]
+
+    def get_pypi_token(self, name: str) -> str | None:
+        return self._password_manager.get_pypi_token(name)
+
+    def get_http_auth(
+        self, name: str, username: str | None = None
+    ) -> HTTPAuthCredential | None:
+        if name == "pypi":
+            repository = AuthenticatorRepositoryConfig(
+                name, "https://upload.pypi.org/legacy/"
+            )
+        else:
+            if name not in self.configured_repositories:
+                return None
+            repository = self.configured_repositories[name]
+
+        return self._get_credentials_for_repository(
+            repository=repository, username=username
+        )
+
+    def get_certs_for_repository(self, name: str) -> RepositoryCertificateConfig:
+        if name.lower() == "pypi" or name not in self.configured_repositories:
+            return RepositoryCertificateConfig()
+        return self.configured_repositories[name].certs(self._config)
+
+    @property
+    def configured_repositories(self) -> dict[str, AuthenticatorRepositoryConfig]:
+        if self._configured_repositories is None:
+            self._configured_repositories = {}
+            for repository_name in self._config.get("repositories", []):
+                url = self._config.get(f"repositories.{repository_name}.url")
+                self._configured_repositories[
+                    repository_name
+                ] = AuthenticatorRepositoryConfig(repository_name, url)
+
+        return self._configured_repositories
+
+    def reset_credentials_cache(self) -> None:
+        self.get_repository_config_for_url.cache_clear()
+        self._credentials = {}
+
+    def add_repository(self, name: str, url: str) -> None:
+        self.configured_repositories[name] = AuthenticatorRepositoryConfig(name, url)
+        self.reset_credentials_cache()
+
+    def get_certs_for_url(self, url: str) -> RepositoryCertificateConfig:
+        if url not in self._certs:
+            self._certs[url] = self._get_certs_for_url(url)
+        return self._certs[url]
+
+    def _get_repository_config_for_url(
+        self, url: str, exact_match: bool = False
+    ) -> AuthenticatorRepositoryConfig | None:
+        parsed_url = urllib.parse.urlsplit(url)
+        candidates_netloc_only = []
+        candidates_path_match = []
+
+        for repository in self.configured_repositories.values():
+            if exact_match:
+                if parsed_url.path == repository.path:
+                    return repository
+                continue
+
+            if repository.netloc == parsed_url.netloc:
+                if parsed_url.path.startswith(repository.path) or commonprefix(
+                    (parsed_url.path, repository.path)
+                ):
+                    candidates_path_match.append(repository)
+                    continue
+                candidates_netloc_only.append(repository)
+
+        if candidates_path_match:
+            candidates = candidates_path_match
+        elif candidates_netloc_only:
+            candidates = candidates_netloc_only
+        else:
+            return None
+
+        if len(candidates) > 1:
+            logger.debug(
+                "Multiple source configurations found for %s - %s",
+                parsed_url.netloc,
+                ", ".join(c.name for c in candidates),
+            )
+            # prefer the more specific path
+            candidates.sort(
+                key=lambda c: len(commonprefix([parsed_url.path, c.path])), reverse=True
+            )
+
+        return candidates[0]
+
+    def _get_certs_for_url(self, url: str) -> RepositoryCertificateConfig:
+        selected = self.get_repository_config_for_url(url)
+        if selected:
+            return selected.certs(config=self._config)
+        return RepositoryCertificateConfig()
+
+
+_authenticator: Authenticator | None = None
+
+
+def get_default_authenticator() -> Authenticator:
+    global _authenticator
+
+    if _authenticator is None:
+        _authenticator = Authenticator()
+
+    return _authenticator
diff --git a/conda_lock/_vendor/poetry/utils/cache.py b/conda_lock/_vendor/poetry/utils/cache.py
new file mode 100644
index 000000000..ba88a0770
--- /dev/null
+++ b/conda_lock/_vendor/poetry/utils/cache.py
@@ -0,0 +1,198 @@
+from __future__ import annotations
+
+import contextlib
+import dataclasses
+import hashlib
+import json
+import shutil
+import time
+
+from pathlib import Path
+from typing import Any
+from typing import Callable
+from typing import Generic
+from typing import TypeVar
+
+
+# Used by Cachy for items that do not expire.
+MAX_DATE = 9999999999
+T = TypeVar("T")
+
+
+def decode(string: bytes, encodings: list[str] | None = None) -> str:
+    """
+    Compatiblity decode function pulled from cachy.
+
+    :param string: The byte string to decode.
+    :param encodings: List of encodings to apply
+    :return: Decoded string
+    """
+    if encodings is None:
+        encodings = ["utf-8", "latin1", "ascii"]
+
+    for encoding in encodings:
+        with contextlib.suppress(UnicodeDecodeError):
+            return string.decode(encoding)
+
+    return string.decode(encodings[0], errors="ignore")
+
+
+def encode(string: str, encodings: list[str] | None = None) -> bytes:
+    """
+    Compatibility encode function from cachy.
+
+    :param string: The string to encode.
+    :param encodings: List of encodings to apply
+    :return: Encoded byte string
+    """
+    if encodings is None:
+        encodings = ["utf-8", "latin1", "ascii"]
+
+    for encoding in encodings:
+        with contextlib.suppress(UnicodeDecodeError):
+            return string.encode(encoding)
+
+    return string.encode(encodings[0], errors="ignore")
+
+
+def _expiration(minutes: int) -> int:
+    """
+    Calculates the time in seconds since epoch that occurs 'minutes' from now.
+
+    :param minutes: The number of minutes to count forward
+    """
+    return round(time.time()) + minutes * 60
+
+
+_HASHES = {
+    "md5": (hashlib.md5, 2),
+    "sha1": (hashlib.sha1, 4),
+    "sha256": (hashlib.sha256, 8),
+}
+
+
+@dataclasses.dataclass(frozen=True)
+class CacheItem(Generic[T]):
+    """
+    Stores data and metadata for cache items.
+    """
+
+    data: T
+    expires: int | None = None
+
+    @property
+    def expired(self) -> bool:
+        """
+        Return true if the cache item has exceeded its expiration period.
+        """
+        return self.expires is not None and time.time() >= self.expires
+
+
+@dataclasses.dataclass(frozen=True)
+class FileCache(Generic[T]):
+    """
+    Cachy-compatible minimal file cache. Stores subsequent data in a JSON format.
+
+    :param path: The path that the cache starts at.
+    :param hash_type: The hash to use for encoding keys/building directories.
+    """
+
+    path: Path
+    hash_type: str = "sha256"
+
+    def __post_init__(self) -> None:
+        if self.hash_type not in _HASHES:
+            raise ValueError(
+                f"FileCache.hash_type is unknown value: '{self.hash_type}'."
+            )
+
+    def get(self, key: str) -> T | None:
+        return self._get_payload(key)
+
+    def has(self, key: str) -> bool:
+        """
+        Determine if a file exists and has not expired in the cache.
+        :param key: The cache key
+        :returns: True if the key exists in the cache
+        """
+        return self.get(key) is not None
+
+    def put(self, key: str, value: Any, minutes: int | None = None) -> None:
+        """
+        Store an item in the cache.
+
+        :param key: The cache key
+        :param value: The cache value
+        :param minutes: The lifetime in minutes of the cached value
+        """
+        payload: CacheItem[Any] = CacheItem(
+            value, expires=_expiration(minutes) if minutes is not None else None
+        )
+        path = self._path(key)
+        path.parent.mkdir(parents=True, exist_ok=True)
+        with open(path, "wb") as f:
+            f.write(self._serialize(payload))
+
+    def forget(self, key: str) -> None:
+        """
+        Remove an item from the cache.
+
+        :param key: The cache key
+        """
+        path = self._path(key)
+        if path.exists():
+            path.unlink()
+
+    def flush(self) -> None:
+        """
+        Clear the cache.
+        """
+        shutil.rmtree(self.path)
+
+    def remember(
+        self, key: str, callback: T | Callable[[], T], minutes: int | None = None
+    ) -> T:
+        """
+        Get an item from the cache, or use a default from callback.
+
+        :param key: The cache key
+        :param callback: Callback function providing default value
+        :param minutes: The lifetime in minutes of the cached value
+        """
+        value = self.get(key)
+        if value is None:
+            value = callback() if callable(callback) else callback
+            self.put(key, value, minutes)
+        return value
+
+    def _get_payload(self, key: str) -> T | None:
+        path = self._path(key)
+
+        if not path.exists():
+            return None
+
+        with open(path, "rb") as f:
+            payload = self._deserialize(f.read())
+
+        if payload.expired:
+            self.forget(key)
+            return None
+        else:
+            return payload.data
+
+    def _path(self, key: str) -> Path:
+        hash_type, parts_count = _HASHES[self.hash_type]
+        h = hash_type(encode(key)).hexdigest()
+        parts = [h[i : i + 2] for i in range(0, len(h), 2)][:parts_count]
+        return Path(self.path, *parts, h)
+
+    def _serialize(self, payload: CacheItem[T]) -> bytes:
+        expires = payload.expires or MAX_DATE
+        data = json.dumps(payload.data)
+        return encode(f"{expires:010d}{data}")
+
+    def _deserialize(self, data_raw: bytes) -> CacheItem[T]:
+        data_str = decode(data_raw)
+        data = json.loads(data_str[10:])
+        expires = int(data_str[:10])
+        return CacheItem(data, expires)
diff --git a/conda_lock/_vendor/poetry/utils/constants.py b/conda_lock/_vendor/poetry/utils/constants.py
new file mode 100644
index 000000000..0f799b16d
--- /dev/null
+++ b/conda_lock/_vendor/poetry/utils/constants.py
@@ -0,0 +1,5 @@
+from __future__ import annotations
+
+
+# Timeout for HTTP requests using the requests library.
+REQUESTS_TIMEOUT = 15
diff --git a/conda_lock/_vendor/poetry/utils/dependency_specification.py b/conda_lock/_vendor/poetry/utils/dependency_specification.py
new file mode 100644
index 000000000..6760767ee
--- /dev/null
+++ b/conda_lock/_vendor/poetry/utils/dependency_specification.py
@@ -0,0 +1,226 @@
+from __future__ import annotations
+
+import contextlib
+import os
+import re
+import urllib.parse
+
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Dict
+from typing import List
+from typing import TypeVar
+from typing import Union
+from typing import cast
+
+from conda_lock._vendor.poetry.core.packages.dependency import Dependency
+from tomlkit.items import InlineTable
+
+from conda_lock._vendor.poetry.puzzle.provider import Provider
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.vcs_dependency import VCSDependency
+
+    from conda_lock._vendor.poetry.utils.env import Env
+
+
+DependencySpec = Dict[str, Union[str, bool, Dict[str, Union[str, bool]], List[str]]]
+
+
+def _parse_dependency_specification_git_url(
+    requirement: str, env: Env | None = None
+) -> DependencySpec | None:
+    from conda_lock._vendor.poetry.core.vcs.git import Git
+    from conda_lock._vendor.poetry.core.vcs.git import ParsedUrl
+
+    parsed = ParsedUrl.parse(requirement)
+    url = Git.normalize_url(requirement)
+
+    pair = {"name": parsed.name, "git": url.url}
+
+    if parsed.rev:
+        pair["rev"] = url.revision
+
+    if parsed.subdirectory:
+        pair["subdirectory"] = parsed.subdirectory
+
+    source_root = env.path.joinpath("src") if env else None
+    package = Provider.get_package_from_vcs(
+        "git",
+        url=url.url,
+        rev=pair.get("rev"),
+        subdirectory=parsed.subdirectory,
+        source_root=source_root,
+    )
+    pair["name"] = package.name
+    return pair
+
+
+def _parse_dependency_specification_url(
+    requirement: str, env: Env | None = None
+) -> DependencySpec | None:
+    url_parsed = urllib.parse.urlparse(requirement)
+    if not (url_parsed.scheme and url_parsed.netloc):
+        return None
+
+    if url_parsed.scheme in ["git+https", "git+ssh"]:
+        return _parse_dependency_specification_git_url(requirement, env)
+
+    if url_parsed.scheme in ["http", "https"]:
+        package = Provider.get_package_from_url(requirement)
+        assert package.source_url is not None
+        return {"name": package.name, "url": package.source_url}
+
+    return None
+
+
+def _parse_dependency_specification_path(
+    requirement: str, cwd: Path
+) -> DependencySpec | None:
+    if (os.path.sep in requirement or "/" in requirement) and (
+        cwd.joinpath(requirement).exists()
+        or Path(requirement).expanduser().exists()
+        and Path(requirement).expanduser().is_absolute()
+    ):
+        path = Path(requirement).expanduser()
+        is_absolute = path.is_absolute()
+
+        if not path.is_absolute():
+            path = cwd.joinpath(requirement)
+
+        if path.is_file():
+            package = Provider.get_package_from_file(path.resolve())
+        else:
+            package = Provider.get_package_from_directory(path.resolve())
+
+        return {
+            "name": package.name,
+            "path": path.relative_to(cwd).as_posix()
+            if not is_absolute
+            else path.as_posix(),
+        }
+
+    return None
+
+
+def _parse_dependency_specification_simple(
+    requirement: str,
+) -> DependencySpec | None:
+    extras: list[str] = []
+    pair = re.sub("^([^@=: ]+)(?:@|==|(?~!])=|:| )(.*)$", "\\1 \\2", requirement)
+    pair = pair.strip()
+
+    require: DependencySpec = {}
+
+    if " " in pair:
+        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
+        if version != "latest":
+            require["version"] = version
+    else:
+        m = re.match(r"^([^><=!: ]+)((?:>=|<=|>|<|!=|~=|~|\^).*)$", requirement.strip())
+        if m:
+            name, constraint = m.group(1), m.group(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["version"] = constraint
+        else:
+            extras_m = re.search(r"\[([\w\d,-_]+)\]$", pair)
+            if extras_m:
+                extras = [e.strip() for e in extras_m.group(1).split(",")]
+                pair, _ = pair.split("[")
+
+            require["name"] = pair
+
+    if extras:
+        require["extras"] = extras
+
+    return require
+
+
+BaseSpec = TypeVar("BaseSpec", DependencySpec, InlineTable)
+
+
+def dependency_to_specification(
+    dependency: Dependency, specification: BaseSpec
+) -> BaseSpec:
+    if dependency.is_vcs():
+        dependency = cast("VCSDependency", dependency)
+        assert dependency.source_url is not None
+        specification[dependency.vcs] = dependency.source_url
+        if dependency.reference:
+            specification["rev"] = dependency.reference
+    elif dependency.is_file() or dependency.is_directory():
+        assert dependency.source_url is not None
+        specification["path"] = dependency.source_url
+    elif dependency.is_url():
+        assert dependency.source_url is not None
+        specification["url"] = dependency.source_url
+    elif dependency.pretty_constraint != "*" and not dependency.constraint.is_empty():
+        specification["version"] = dependency.pretty_constraint
+
+    if not dependency.marker.is_any():
+        specification["markers"] = str(dependency.marker)
+
+    if dependency.extras:
+        specification["extras"] = sorted(dependency.extras)
+
+    return specification
+
+
+def pep508_to_dependency_specification(requirement: str) -> DependencySpec | None:
+    if " ; " not in requirement and re.search(r"@[\^~!=<>\d]", requirement):
+        # this is of the form package@, do not attempt to parse it
+        return None
+
+    with contextlib.suppress(ValueError):
+        dependency = Dependency.create_from_pep_508(requirement)
+        specification: DependencySpec = {}
+        specification = dependency_to_specification(dependency, specification)
+
+        if specification:
+            specification["name"] = dependency.name
+            return specification
+
+    return None
+
+
+def parse_dependency_specification(
+    requirement: str, env: Env | None = None, cwd: Path | None = None
+) -> DependencySpec:
+    requirement = requirement.strip()
+    cwd = cwd or Path.cwd()
+
+    specification = pep508_to_dependency_specification(requirement)
+
+    if specification is not None:
+        return specification
+
+    extras = []
+    extras_m = re.search(r"\[([\w\d,-_ ]+)\]$", requirement)
+    if extras_m:
+        extras = [e.strip() for e in extras_m.group(1).split(",")]
+        requirement, _ = requirement.split("[")
+
+    specification = (
+        _parse_dependency_specification_url(requirement, env=env)
+        or _parse_dependency_specification_path(requirement, cwd=cwd)
+        or _parse_dependency_specification_simple(requirement)
+    )
+
+    if specification:
+        if extras and "extras" not in specification:
+            specification["extras"] = extras
+        return specification
+
+    raise ValueError(f"Invalid dependency specification: {requirement}")
diff --git a/conda_lock/_vendor/poetry/utils/env.py b/conda_lock/_vendor/poetry/utils/env.py
index 9ca1e68e2..df9b714da 100644
--- a/conda_lock/_vendor/poetry/utils/env.py
+++ b/conda_lock/_vendor/poetry/utils/env.py
@@ -1,47 +1,89 @@
+from __future__ import annotations
+
 import base64
+import contextlib
 import hashlib
+import itertools
 import json
 import os
 import platform
+import plistlib
 import re
-import shutil
+import subprocess
 import sys
 import sysconfig
-import textwrap
+import warnings
 
 from contextlib import contextmanager
 from copy import deepcopy
+from pathlib import Path
+from subprocess import CalledProcessError
+from typing import TYPE_CHECKING
 from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Union
+from typing import cast
 
 import packaging.tags
 import tomlkit
 import virtualenv
 
-from clikit.api.io import IO
+from conda_lock._vendor.cleo.io.null_io import NullIO
+from conda_lock._vendor.cleo.io.outputs.output import Verbosity
 from packaging.tags import Tag
 from packaging.tags import interpreter_name
 from packaging.tags import interpreter_version
 from packaging.tags import sys_tags
-
-from conda_lock._vendor.poetry.core.semver import parse_constraint
-from conda_lock._vendor.poetry.core.semver.version import Version
+from conda_lock._vendor.poetry.core.constraints.version import Version
+from conda_lock._vendor.poetry.core.constraints.version import parse_constraint
 from conda_lock._vendor.poetry.core.toml.file import TOMLFile
-from conda_lock._vendor.poetry.core.version.markers import BaseMarker
-from conda_lock._vendor.poetry.locations import CACHE_DIR
-from conda_lock._vendor.poetry.poetry import Poetry
-from conda_lock._vendor.poetry.utils._compat import CalledProcessError
-from conda_lock._vendor.poetry.utils._compat import Path
+from conda_lock._vendor.poetry.core.utils.helpers import temporary_directory
+from virtualenv.seed.wheels.embed import get_embed_wheel
+
+from conda_lock._vendor.poetry.utils._compat import WINDOWS
 from conda_lock._vendor.poetry.utils._compat import decode
 from conda_lock._vendor.poetry.utils._compat import encode
 from conda_lock._vendor.poetry.utils._compat import list_to_shell_command
-from conda_lock._vendor.poetry.utils._compat import subprocess
+from conda_lock._vendor.poetry.utils._compat import metadata
+from conda_lock._vendor.poetry.utils.helpers import get_real_windows_path
 from conda_lock._vendor.poetry.utils.helpers import is_dir_writable
 from conda_lock._vendor.poetry.utils.helpers import paths_csv
+from conda_lock._vendor.poetry.utils.helpers import remove_directory
+
+
+if TYPE_CHECKING:
+    from collections.abc import Iterable
+    from collections.abc import Iterator
+
+    from conda_lock._vendor.cleo.io.io import IO
+    from conda_lock._vendor.poetry.core.poetry import Poetry as CorePoetry
+    from conda_lock._vendor.poetry.core.version.markers import BaseMarker
+    from virtualenv.seed.wheels.util import Wheel
+
+    from conda_lock._vendor.poetry.poetry import Poetry
+
+
+GET_SYS_TAGS = f"""
+import importlib.util
+import json
+import sys
+
+from pathlib import Path
+
+spec = importlib.util.spec_from_file_location(
+    "packaging", Path(r"{packaging.__file__}")
+)
+packaging = importlib.util.module_from_spec(spec)
+sys.modules[spec.name] = packaging
+
+spec = importlib.util.spec_from_file_location(
+    "packaging.tags", Path(r"{packaging.tags.__file__}")
+)
+packaging_tags = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(packaging_tags)
+
+print(
+    json.dumps([(t.interpreter, t.abi, t.platform) for t in packaging_tags.sys_tags()])
+)
+"""
 
 
 GET_ENVIRONMENT_INFO = """\
@@ -71,7 +113,6 @@ def interpreter_version():
 
 
 def _version_nodot(version):
-    # type: (PythonVersion) -> str
     if any(v >= 10 for v in version):
         sep = "_"
     else:
@@ -106,7 +147,9 @@ def _version_nodot(version):
     "sys_platform": sys.platform,
     "version_info": tuple(sys.version_info),
     # Extra information
-    "interpreter_name": INTERPRETER_SHORT_NAMES.get(implementation_name, implementation_name),
+    "interpreter_name": INTERPRETER_SHORT_NAMES.get(
+        implementation_name, implementation_name
+    ),
     "interpreter_version": interpreter_version(),
 }
 
@@ -131,6 +174,11 @@ def _version_nodot(version):
 print('.'.join([str(s) for s in sys.version_info[:3]]))
 """
 
+GET_PYTHON_VERSION_ONELINER = (
+    "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\""
+)
+GET_ENV_PATH_ONELINER = '"import sys; print(sys.prefix)"'
+
 GET_SYS_PATH = """\
 import json
 import sys
@@ -154,7 +202,7 @@ def _version_nodot(version):
 import site
 import sysconfig
 
-from distutils.command.install import SCHEME_KEYS  # noqa
+from distutils.command.install import SCHEME_KEYS
 from distutils.core import Distribution
 
 d = Distribution()
@@ -180,24 +228,46 @@ def _version_nodot(version):
 
 class SitePackages:
     def __init__(
-        self, path, fallbacks=None, skip_write_checks=False
-    ):  # type: (Path, List[Path], bool) -> None
-        self._path = path
+        self,
+        purelib: Path,
+        platlib: Path | None = None,
+        fallbacks: list[Path] | None = None,
+        skip_write_checks: bool = False,
+    ) -> None:
+        self._purelib = purelib
+        self._platlib = platlib or purelib
+
+        if platlib and platlib.resolve() == purelib.resolve():
+            self._platlib = purelib
+
         self._fallbacks = fallbacks or []
         self._skip_write_checks = skip_write_checks
-        self._candidates = [self._path] + self._fallbacks
+
+        self._candidates: list[Path] = []
+        for path in itertools.chain([self._purelib, self._platlib], self._fallbacks):
+            if path not in self._candidates:
+                self._candidates.append(path)
+
         self._writable_candidates = None if not skip_write_checks else self._candidates
 
     @property
-    def path(self):  # type: () -> Path
-        return self._path
+    def path(self) -> Path:
+        return self._purelib
 
     @property
-    def candidates(self):  # type: () -> List[Path]
+    def purelib(self) -> Path:
+        return self._purelib
+
+    @property
+    def platlib(self) -> Path:
+        return self._platlib
+
+    @property
+    def candidates(self) -> list[Path]:
         return self._candidates
 
     @property
-    def writable_candidates(self):  # type: () -> List[Path]
+    def writable_candidates(self) -> list[Path]:
         if self._writable_candidates is not None:
             return self._writable_candidates
 
@@ -210,41 +280,132 @@ def writable_candidates(self):  # type: () -> List[Path]
         return self._writable_candidates
 
     def make_candidates(
-        self, path, writable_only=False
-    ):  # type: (Path, bool) -> List[Path]
+        self, path: Path, writable_only: bool = False, strict: bool = False
+    ) -> list[Path]:
         candidates = self._candidates if not writable_only else self.writable_candidates
         if path.is_absolute():
             for candidate in candidates:
-                try:
+                with contextlib.suppress(ValueError):
                     path.relative_to(candidate)
                     return [path]
-                except ValueError:
-                    pass
-            else:
-                raise ValueError(
-                    "{} is not relative to any discovered {}sites".format(
-                        path, "writable " if writable_only else ""
+            site_type = "writable " if writable_only else ""
+            raise ValueError(
+                f"{path} is not relative to any discovered {site_type}sites"
+            )
+
+        results = [candidate / path for candidate in candidates]
+
+        if not results and strict:
+            raise RuntimeError(
+                f'Unable to find a suitable destination for "{path}" in'
+                f" {paths_csv(self._candidates)}"
+            )
+
+        return results
+
+    def distributions(
+        self, name: str | None = None, writable_only: bool = False
+    ) -> Iterable[metadata.Distribution]:
+        path = list(
+            map(
+                str, self._candidates if not writable_only else self.writable_candidates
+            )
+        )
+
+        yield from metadata.PathDistribution.discover(  # type: ignore[no-untyped-call]
+            name=name,
+            path=path,
+        )
+
+    def find_distribution(
+        self, name: str, writable_only: bool = False
+    ) -> metadata.Distribution | None:
+        for distribution in self.distributions(name=name, writable_only=writable_only):
+            return distribution
+        return None
+
+    def find_distribution_files_with_suffix(
+        self, distribution_name: str, suffix: str, writable_only: bool = False
+    ) -> Iterable[Path]:
+        for distribution in self.distributions(
+            name=distribution_name, writable_only=writable_only
+        ):
+            files = [] if distribution.files is None else distribution.files
+            for file in files:
+                if file.name.endswith(suffix):
+                    yield Path(
+                        distribution.locate_file(file),  # type: ignore[no-untyped-call]
                     )
-                )
 
-        return [candidate / path for candidate in candidates if candidate]
+    def find_distribution_files_with_name(
+        self, distribution_name: str, name: str, writable_only: bool = False
+    ) -> Iterable[Path]:
+        for distribution in self.distributions(
+            name=distribution_name, writable_only=writable_only
+        ):
+            files = [] if distribution.files is None else distribution.files
+            for file in files:
+                if file.name == name:
+                    yield Path(
+                        distribution.locate_file(file),  # type: ignore[no-untyped-call]
+                    )
 
-    def _path_method_wrapper(
-        self, path, method, *args, **kwargs
-    ):  # type: (Path, str, *Any, **Any) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]]
+    def find_distribution_nspkg_pth_files(
+        self, distribution_name: str, writable_only: bool = False
+    ) -> Iterable[Path]:
+        return self.find_distribution_files_with_suffix(
+            distribution_name=distribution_name,
+            suffix="-nspkg.pth",
+            writable_only=writable_only,
+        )
 
-        # TODO: Move to parameters after dropping Python 2.7
-        return_first = kwargs.pop("return_first", True)
-        writable_only = kwargs.pop("writable_only", False)
+    def find_distribution_direct_url_json_files(
+        self, distribution_name: str, writable_only: bool = False
+    ) -> Iterable[Path]:
+        return self.find_distribution_files_with_name(
+            distribution_name=distribution_name,
+            name="direct_url.json",
+            writable_only=writable_only,
+        )
 
-        candidates = self.make_candidates(path, writable_only=writable_only)
+    def remove_distribution_files(self, distribution_name: str) -> list[Path]:
+        paths = []
 
-        if not candidates:
-            raise RuntimeError(
-                'Unable to find a suitable destination for "{}" in {}'.format(
-                    str(path), paths_csv(self._candidates)
+        for distribution in self.distributions(
+            name=distribution_name, writable_only=True
+        ):
+            files = [] if distribution.files is None else distribution.files
+            for file in files:
+                path = Path(
+                    distribution.locate_file(file),  # type: ignore[no-untyped-call]
                 )
-            )
+                # We can't use unlink(missing_ok=True) because it's not always available
+                if path.exists():
+                    path.unlink()
+
+            distribution_path: Path = distribution._path  # type: ignore[attr-defined]
+            if distribution_path.exists():
+                remove_directory(str(distribution_path), force=True)
+
+            paths.append(distribution_path)
+
+        return paths
+
+    def _path_method_wrapper(
+        self,
+        path: str | Path,
+        method: str,
+        *args: Any,
+        return_first: bool = True,
+        writable_only: bool = False,
+        **kwargs: Any,
+    ) -> tuple[Path, Any] | list[tuple[Path, Any]]:
+        if isinstance(path, str):
+            path = Path(path)
+
+        candidates = self.make_candidates(
+            path, writable_only=writable_only, strict=True
+        )
 
         results = []
 
@@ -253,30 +414,37 @@ def _path_method_wrapper(
                 result = candidate, getattr(candidate, method)(*args, **kwargs)
                 if return_first:
                     return result
-                else:
-                    results.append(result)
-            except (IOError, OSError):
+                results.append(result)
+            except OSError:
                 # TODO: Replace with PermissionError
                 pass
 
         if results:
             return results
 
-        raise OSError("Unable to access any of {}".format(paths_csv(candidates)))
+        raise OSError(f"Unable to access any of {paths_csv(candidates)}")
 
-    def write_text(self, path, *args, **kwargs):  # type: (Path, *Any, **Any) -> Path
-        return self._path_method_wrapper(path, "write_text", *args, **kwargs)[0]
+    def write_text(self, path: str | Path, *args: Any, **kwargs: Any) -> Path:
+        paths = self._path_method_wrapper(path, "write_text", *args, **kwargs)
+        assert isinstance(paths, tuple)
+        return paths[0]
 
-    def mkdir(self, path, *args, **kwargs):  # type: (Path, *Any, **Any) -> Path
-        return self._path_method_wrapper(path, "mkdir", *args, **kwargs)[0]
+    def mkdir(self, path: str | Path, *args: Any, **kwargs: Any) -> Path:
+        paths = self._path_method_wrapper(path, "mkdir", *args, **kwargs)
+        assert isinstance(paths, tuple)
+        return paths[0]
 
-    def exists(self, path):  # type: (Path) -> bool
+    def exists(self, path: str | Path) -> bool:
         return any(
             value[-1]
             for value in self._path_method_wrapper(path, "exists", return_first=False)
         )
 
-    def find(self, path, writable_only=False):  # type: (Path, bool) -> List[Path]
+    def find(
+        self,
+        path: str | Path,
+        writable_only: bool = False,
+    ) -> list[Path]:
         return [
             value[0]
             for value in self._path_method_wrapper(
@@ -285,39 +453,39 @@ def find(self, path, writable_only=False):  # type: (Path, bool) -> List[Path]
             if value[-1] is True
         ]
 
-    def __getattr__(self, item):
-        try:
-            return super(SitePackages, self).__getattribute__(item)
-        except AttributeError:
-            return getattr(self.path, item)
-
 
 class EnvError(Exception):
-
     pass
 
 
+class IncorrectEnvError(EnvError):
+    def __init__(self, env_name: str) -> None:
+        message = f"Env {env_name} doesn't belong to this project."
+        super().__init__(message)
+
+
 class EnvCommandError(EnvError):
-    def __init__(self, e, input=None):  # type: (CalledProcessError) -> None
+    def __init__(self, e: CalledProcessError, input: str | None = None) -> None:
         self.e = e
 
-        message = "Command {} errored with the following return code {}, and output: \n{}".format(
-            e.cmd, e.returncode, decode(e.output)
+        message = (
+            f"Command {e.cmd} errored with the following return code {e.returncode},"
+            f" and output: \n{decode(e.output)}"
         )
         if input:
-            message += "input was : {}".format(input)
-        super(EnvCommandError, self).__init__(message)
+            message += f"input was : {input}"
+        super().__init__(message)
 
 
 class NoCompatiblePythonVersionFound(EnvError):
-    def __init__(self, expected, given=None):
+    def __init__(self, expected: str, given: str | None = None) -> None:
         if given:
             message = (
-                "The specified Python version ({}) "
-                "is not supported by the project ({}).\n"
+                f"The specified Python version ({given}) "
+                f"is not supported by the project ({expected}).\n"
                 "Please choose a compatible version "
                 "or loosen the python constraint specified "
-                "in the pyproject.toml file.".format(given, expected)
+                "in the pyproject.toml file."
             )
         else:
             message = (
@@ -326,10 +494,21 @@ def __init__(self, expected, given=None):
                 'via the "env use" command.'
             )
 
-        super(NoCompatiblePythonVersionFound, self).__init__(message)
+        super().__init__(message)
 
 
-class EnvManager(object):
+class InvalidCurrentPythonVersionError(EnvError):
+    def __init__(self, expected: str, given: str) -> None:
+        message = (
+            f"Current Python version ({given}) "
+            f"is not allowed by the project ({expected}).\n"
+            'Please change python executable via the "env use" command.'
+        )
+
+        super().__init__(message)
+
+
+class EnvManager:
     """
     Environments manager
     """
@@ -338,47 +517,95 @@ class EnvManager(object):
 
     ENVS_FILE = "envs.toml"
 
-    def __init__(self, poetry):  # type: (Poetry) -> None
+    def __init__(self, poetry: Poetry, io: None | IO = None) -> None:
         self._poetry = poetry
+        self._io = io or NullIO()
 
-    def activate(self, python, io):  # type: (str, IO) -> Env
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = Path(CACHE_DIR) / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
+    def _full_python_path(self, python: str) -> str:
+        try:
+            executable = decode(
+                subprocess.check_output(
+                    list_to_shell_command(
+                        [python, "-c", '"import sys; print(sys.executable)"']
+                    ),
+                    shell=True,
+                ).strip()
+            )
+        except CalledProcessError as e:
+            raise EnvCommandError(e)
+
+        return executable
+
+    def _detect_active_python(self) -> str | None:
+        executable = None
+
+        try:
+            self._io.write_error_line(
+                "Trying to detect current active python executable as specified in the"
+                " config.",
+                verbosity=Verbosity.VERBOSE,
+            )
+            executable = self._full_python_path("python")
+            self._io.write_error_line(
+                f"Found: {executable}", verbosity=Verbosity.VERBOSE
+            )
+        except CalledProcessError:
+            self._io.write_error_line(
+                "Unable to detect the current active python executable. Falling back to"
+                " default.",
+                verbosity=Verbosity.VERBOSE,
+            )
+        return executable
+
+    def _get_python_version(self) -> tuple[int, int, int]:
+        version_info = tuple(sys.version_info[:3])
+
+        if self._poetry.config.get("virtualenvs.prefer-active-python"):
+            executable = self._detect_active_python()
+
+            if executable:
+                python_patch = decode(
+                    subprocess.check_output(
+                        list_to_shell_command(
+                            [executable, "-c", GET_PYTHON_VERSION_ONELINER]
+                        ),
+                        shell=True,
+                    ).strip()
+                )
+
+                version_info = tuple(int(v) for v in python_patch.split(".")[:3])
 
+        return cast("tuple[int, int, int]", version_info)
+
+    def activate(self, python: str) -> Env:
+        venv_path = self._poetry.config.virtualenvs_path
         cwd = self._poetry.file.parent
 
         envs_file = TOMLFile(venv_path / self.ENVS_FILE)
 
         try:
             python_version = Version.parse(python)
-            python = "python{}".format(python_version.major)
+            python = f"python{python_version.major}"
             if python_version.precision > 1:
-                python += ".{}".format(python_version.minor)
+                python += f".{python_version.minor}"
         except ValueError:
             # Executable in PATH or full executable path
             pass
 
+        python = self._full_python_path(python)
+
         try:
-            python_version = decode(
+            python_version_string = decode(
                 subprocess.check_output(
-                    list_to_shell_command(
-                        [
-                            python,
-                            "-c",
-                            "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
-                        ]
-                    ),
+                    list_to_shell_command([python, "-c", GET_PYTHON_VERSION_ONELINER]),
                     shell=True,
                 )
             )
         except CalledProcessError as e:
             raise EnvCommandError(e)
 
-        python_version = Version.parse(python_version.strip())
-        minor = "{}.{}".format(python_version.major, python_version.minor)
+        python_version = Version.parse(python_version_string.strip())
+        minor = f"{python_version.major}.{python_version.minor}"
         patch = python_version.text
 
         create = False
@@ -396,7 +623,7 @@ def activate(self, python, io):  # type: (str, IO) -> Env
                 if patch != current_patch:
                     create = True
 
-            self.create_venv(io, executable=python, force=create)
+            self.create_venv(executable=python, force=create)
 
             return self.get(reload=True)
 
@@ -413,7 +640,7 @@ def activate(self, python, io):  # type: (str, IO) -> Env
                     # We need to recreate
                     create = True
 
-        name = "{}-py{}".format(base_env_name, minor)
+        name = f"{base_env_name}-py{minor}"
         venv = venv_path / name
 
         # Create if needed
@@ -430,7 +657,7 @@ def activate(self, python, io):  # type: (str, IO) -> Env
                 if patch != current_patch:
                     create = True
 
-            self.create_venv(io, executable=python, force=create)
+            self.create_venv(executable=python, force=create)
 
         # Activate
         envs[base_env_name] = {"minor": minor, "patch": patch}
@@ -438,41 +665,32 @@ def activate(self, python, io):  # type: (str, IO) -> Env
 
         return self.get(reload=True)
 
-    def deactivate(self, io):  # type: (IO) -> None
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = Path(CACHE_DIR) / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
-
-        name = self._poetry.package.name
-        name = self.generate_env_name(name, str(self._poetry.file.parent))
+    def deactivate(self) -> None:
+        venv_path = self._poetry.config.virtualenvs_path
+        name = self.generate_env_name(
+            self._poetry.package.name, str(self._poetry.file.parent)
+        )
 
         envs_file = TOMLFile(venv_path / self.ENVS_FILE)
         if envs_file.exists():
             envs = envs_file.read()
             env = envs.get(name)
             if env is not None:
-                io.write_line(
-                    "Deactivating virtualenv: {}".format(
-                        venv_path / (name + "-py{}".format(env["minor"]))
-                    )
+                venv = venv_path / f"{name}-py{env['minor']}"
+                self._io.write_error_line(
+                    f"Deactivating virtualenv: {venv}"
                 )
                 del envs[name]
 
                 envs_file.write(envs)
 
-    def get(self, reload=False):  # type: (bool) -> Env
+    def get(self, reload: bool = False) -> Env:
         if self._env is not None and not reload:
             return self._env
 
-        python_minor = ".".join([str(v) for v in sys.version_info[:2]])
+        python_minor = ".".join([str(v) for v in self._get_python_version()[:2]])
 
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = Path(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)
@@ -495,24 +713,23 @@ def get(self, reload=False):  # type: (bool) -> Env
 
         if not in_venv or env is not None:
             # Checking if a local virtualenv exists
-            if self._poetry.config.get("virtualenvs.in-project") is not False:
-                if (cwd / ".venv").exists() and (cwd / ".venv").is_dir():
-                    venv = cwd / ".venv"
+            if (
+                self._poetry.config.get("virtualenvs.in-project") is not False
+                and (cwd / ".venv").exists()
+                and (cwd / ".venv").is_dir()
+            ):
+                venv = cwd / ".venv"
 
-                    return VirtualEnv(venv)
+                return VirtualEnv(venv)
 
             create_venv = self._poetry.config.get("virtualenvs.create", True)
 
             if not create_venv:
                 return self.get_system_env()
 
-            venv_path = self._poetry.config.get("virtualenvs.path")
-            if venv_path is None:
-                venv_path = Path(CACHE_DIR) / "virtualenvs"
-            else:
-                venv_path = Path(venv_path)
+            venv_path = self._poetry.config.virtualenvs_path
 
-            name = "{}-py{}".format(base_env_name, python_minor.strip())
+            name = f"{base_env_name}-py{python_minor.strip()}"
 
             venv = venv_path / name
 
@@ -530,44 +747,58 @@ def get(self, reload=False):  # type: (bool) -> Env
 
         return VirtualEnv(prefix, base_prefix)
 
-    def list(self, name=None):  # type: (Optional[str]) -> List[VirtualEnv]
+    def list(self, name: str | None = None) -> list[VirtualEnv]:
         if name is None:
             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 = Path(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("{}-py*".format(venv_name)))
+            VirtualEnv(Path(p)) for p in sorted(venv_path.glob(f"{venv_name}-py*"))
         ]
 
         venv = self._poetry.file.parent / ".venv"
         if (
-            self._poetry.config.get("virtualenvs.in-project")
+            self._poetry.config.get("virtualenvs.in-project") is not False
             and venv.exists()
             and venv.is_dir()
         ):
             env_list.insert(0, VirtualEnv(venv))
         return env_list
 
-    def remove(self, python):  # type: (str) -> Env
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if venv_path is None:
-            venv_path = Path(CACHE_DIR) / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
+    @staticmethod
+    def check_env_is_for_current_project(env: str, base_env_name: str) -> bool:
+        """
+        Check if env name starts with projects name.
+
+        This is done to prevent action on other project's envs.
+        """
+        return env.startswith(base_env_name)
+
+    def remove(self, python: str) -> Env:
+        venv_path = self._poetry.config.virtualenvs_path
 
         cwd = self._poetry.file.parent
         envs_file = TOMLFile(venv_path / self.ENVS_FILE)
         base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd))
 
-        if python.startswith(base_env_name):
+        python_path = Path(python)
+        if python_path.is_file():
+            # Validate env name if provided env is a full path to python
+            try:
+                env_dir = decode(
+                    subprocess.check_output(
+                        list_to_shell_command([python, "-c", GET_ENV_PATH_ONELINER]),
+                        shell=True,
+                    )
+                ).strip("\n")
+                env_name = Path(env_dir).name
+                if not self.check_env_is_for_current_project(env_name, base_env_name):
+                    raise IncorrectEnvError(env_name)
+            except CalledProcessError as e:
+                raise EnvCommandError(e)
+
+        if self.check_env_is_for_current_project(python, base_env_name):
             venvs = self.list()
             for venv in venvs:
                 if venv.path.name == python:
@@ -596,44 +827,42 @@ def remove(self, python):  # type: (str) -> Env
                     return venv
 
             raise ValueError(
-                'Environment "{}" does not exist.'.format(python)
+                f'Environment "{python}" does not exist.'
             )
+        else:
+            venv_path = self._poetry.config.virtualenvs_path
+            # Get all the poetry envs, even for other projects
+            env_names = [Path(p).name for p in sorted(venv_path.glob("*-*-py*"))]
+            if python in env_names:
+                raise IncorrectEnvError(python)
 
         try:
             python_version = Version.parse(python)
-            python = "python{}".format(python_version.major)
+            python = f"python{python_version.major}"
             if python_version.precision > 1:
-                python += ".{}".format(python_version.minor)
+                python += f".{python_version.minor}"
         except ValueError:
             # Executable in PATH or full executable path
             pass
 
         try:
-            python_version = decode(
+            python_version_string = decode(
                 subprocess.check_output(
-                    list_to_shell_command(
-                        [
-                            python,
-                            "-c",
-                            "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
-                        ]
-                    ),
+                    list_to_shell_command([python, "-c", GET_PYTHON_VERSION_ONELINER]),
                     shell=True,
                 )
             )
         except CalledProcessError as e:
             raise EnvCommandError(e)
 
-        python_version = Version.parse(python_version.strip())
-        minor = "{}.{}".format(python_version.major, python_version.minor)
+        python_version = Version.parse(python_version_string.strip())
+        minor = f"{python_version.major}.{python_version.minor}"
 
-        name = "{}-py{}".format(base_env_name, minor)
-        venv = venv_path / name
+        name = f"{base_env_name}-py{minor}"
+        venv_path = venv_path / name
 
-        if not venv.exists():
-            raise ValueError(
-                'Environment "{}" does not exist.'.format(name)
-            )
+        if not venv_path.exists():
+            raise ValueError(f'Environment "{name}" does not exist.')
 
         if envs_file.exists():
             envs = envs_file.read()
@@ -645,13 +874,16 @@ def remove(self, python):  # type: (str) -> Env
                     del envs[base_env_name]
                     envs_file.write(envs)
 
-        self.remove_venv(venv)
+        self.remove_venv(venv_path)
 
-        return VirtualEnv(venv, venv)
+        return VirtualEnv(venv_path, venv_path)
 
     def create_venv(
-        self, io, name=None, executable=None, force=False
-    ):  # type: (IO, Optional[str], Optional[str], bool) -> Env
+        self,
+        name: str | None = None,
+        executable: str | None = None,
+        force: bool = False,
+    ) -> Env:
         if self._env is not None and not force:
             return self._env
 
@@ -663,21 +895,29 @@ def create_venv(
 
         if env.is_venv() and not force:
             # Already inside a virtualenv.
+            current_python = Version.parse(
+                ".".join(str(c) for c in env.version_info[:3])
+            )
+            if not self._poetry.package.python_constraint.allows(current_python):
+                raise InvalidCurrentPythonVersionError(
+                    self._poetry.package.python_versions, str(current_python)
+                )
             return env
 
         create_venv = self._poetry.config.get("virtualenvs.create")
         root_venv = self._poetry.config.get("virtualenvs.in-project")
+        prefer_active_python = self._poetry.config.get(
+            "virtualenvs.prefer-active-python"
+        )
+        venv_prompt = self._poetry.config.get("virtualenvs.prompt")
 
-        venv_path = self._poetry.config.get("virtualenvs.path")
-        if root_venv:
-            venv_path = cwd / ".venv"
-        elif venv_path is None:
-            venv_path = Path(CACHE_DIR) / "virtualenvs"
-        else:
-            venv_path = Path(venv_path)
+        if not executable and prefer_active_python:
+            executable = self._detect_active_python()
 
+        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
 
         python_patch = ".".join([str(v) for v in sys.version_info[:3]])
         python_minor = ".".join([str(v) for v in sys.version_info[:2]])
@@ -685,11 +925,7 @@ def create_venv(
             python_patch = decode(
                 subprocess.check_output(
                     list_to_shell_command(
-                        [
-                            executable,
-                            "-c",
-                            "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
-                        ]
+                        [executable, "-c", GET_PYTHON_VERSION_ONELINER]
                     ),
                     shell=True,
                 ).strip()
@@ -704,49 +940,42 @@ def create_venv(
             # If an executable has been specified, we stop there
             # and notify the user of the incompatibility.
             # Otherwise, we try to find a compatible Python version.
-            if executable:
+            if executable and not prefer_active_python:
                 raise NoCompatiblePythonVersionFound(
                     self._poetry.package.python_versions, python_patch
                 )
 
-            io.write_line(
-                "The currently activated Python version {} "
-                "is not supported by the project ({}).\n"
-                "Trying to find and use a compatible version. ".format(
-                    python_patch, self._poetry.package.python_versions
-                )
+            self._io.write_error_line(
+                f"The currently activated Python version {python_patch} is not"
+                f" supported by the project ({self._poetry.package.python_versions}).\n"
+                "Trying to find and use a compatible version. "
             )
 
-            for python_to_try in reversed(
-                sorted(
-                    self._poetry.package.AVAILABLE_PYTHONS,
-                    key=lambda v: (v.startswith("3"), -len(v), v),
-                )
+            for python_to_try in sorted(
+                self._poetry.package.AVAILABLE_PYTHONS,
+                key=lambda v: (v.startswith("3"), -len(v), v),
+                reverse=True,
             ):
                 if len(python_to_try) == 1:
-                    if not parse_constraint("^{}.0".format(python_to_try)).allows_any(
+                    if not parse_constraint(f"^{python_to_try}.0").allows_any(
                         supported_python
                     ):
                         continue
-                elif not supported_python.allows_all(
+                elif not supported_python.allows_any(
                     parse_constraint(python_to_try + ".*")
                 ):
                     continue
 
                 python = "python" + python_to_try
 
-                if io.is_debug():
-                    io.write_line("Trying {}".format(python))
+                if self._io.is_debug():
+                    self._io.write_error_line(f"Trying {python}")
 
                 try:
                     python_patch = decode(
                         subprocess.check_output(
                             list_to_shell_command(
-                                [
-                                    python,
-                                    "-c",
-                                    "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"",
-                                ]
+                                [python, "-c", GET_PYTHON_VERSION_ONELINER]
                             ),
                             stderr=subprocess.STDOUT,
                             shell=True,
@@ -759,7 +988,9 @@ def create_venv(
                     continue
 
                 if supported_python.allows(Version.parse(python_patch)):
-                    io.write_line("Using {} ({})".format(python, python_patch))
+                    self._io.write_error_line(
+                        f"Using {python} ({python_patch})"
+                    )
                     executable = python
                     python_minor = ".".join(python_patch.split(".")[:2])
                     break
@@ -773,12 +1004,18 @@ def create_venv(
             venv = venv_path
         else:
             name = self.generate_env_name(name, str(cwd))
-            name = "{}-py{}".format(name, python_minor.strip())
+            name = f"{name}-py{python_minor.strip()}"
             venv = venv_path / name
 
+        if venv_prompt is not None:
+            venv_prompt = venv_prompt.format(
+                project_name=self._poetry.package.name or "virtualenv",
+                python_version=python_minor,
+            )
+
         if not venv.exists():
             if create_venv is False:
-                io.write_line(
+                self._io.write_error_line(
                     ""
                     "Skipping virtualenv creation, "
                     "as specified in config file."
@@ -787,26 +1024,33 @@ def create_venv(
 
                 return self.get_system_env()
 
-            io.write_line(
-                "Creating virtualenv {} in {}".format(name, str(venv_path))
+            self._io.write_error_line(
+                f"Creating virtualenv {name} in"
+                f" {venv_path if not WINDOWS else get_real_windows_path(venv_path)!s}"
             )
-
-            self.build_venv(venv, executable=executable)
         else:
+            create_venv = False
             if force:
                 if not env.is_sane():
-                    io.write_line(
-                        "The virtual environment found in {} seems to be broken.".format(
-                            env.path
-                        )
+                    self._io.write_error_line(
+                        f"The virtual environment found in {env.path} seems to"
+                        " be broken."
                     )
-                io.write_line(
-                    "Recreating virtualenv {} in {}".format(name, str(venv))
+                self._io.write_error_line(
+                    f"Recreating virtualenv {name} in {venv!s}"
                 )
                 self.remove_venv(venv)
-                self.build_venv(venv, executable=executable)
-            elif io.is_very_verbose():
-                io.write_line("Virtualenv {} already exists.".format(name))
+                create_venv = True
+            elif self._io.is_very_verbose():
+                self._io.write_error_line(f"Virtualenv {name} already exists.")
+
+        if create_venv:
+            self.build_venv(
+                venv,
+                executable=executable,
+                flags=self._poetry.config.get("virtualenvs.options"),
+                prompt=venv_prompt,
+            )
 
         # venv detection:
         # stdlib venv may symlink sys.executable, so we can't use realpath.
@@ -822,33 +1066,86 @@ def create_venv(
         p_venv = os.path.normcase(str(venv))
         if any(p.startswith(p_venv) for p in paths):
             # Running properly in the virtualenv, don't need to do anything
-            return SystemEnv(Path(sys.prefix), Path(self.get_base_prefix()))
+            return self.get_system_env()
 
         return VirtualEnv(venv)
 
     @classmethod
     def build_venv(
-        cls, path, executable=None
-    ):  # type: (Union[Path,str], Optional[Union[str, Path]]) -> virtualenv.run.session.Session
+        cls,
+        path: Path | str,
+        executable: str | Path | None = None,
+        flags: dict[str, bool] | None = None,
+        with_pip: bool | None = None,
+        with_wheel: bool | None = None,
+        with_setuptools: bool | None = None,
+        prompt: str | None = None,
+    ) -> virtualenv.run.session.Session:
+        if WINDOWS:
+            path = get_real_windows_path(path)
+            executable = get_real_windows_path(executable) if executable else None
+
+        flags = flags or {}
+
+        flags["no-pip"] = (
+            not with_pip if with_pip is not None else flags.pop("no-pip", True)
+        )
+
+        flags["no-setuptools"] = (
+            not with_setuptools
+            if with_setuptools is not None
+            else flags.pop("no-setuptools", True)
+        )
+
+        # we want wheels to be enabled when pip is required and it has not been
+        # explicitly disabled
+        flags["no-wheel"] = (
+            not with_wheel
+            if with_wheel is not None
+            else flags.pop("no-wheel", flags["no-pip"])
+        )
+
         if isinstance(executable, Path):
             executable = executable.resolve().as_posix()
-        return virtualenv.cli_run(
-            [
-                "--no-download",
-                "--no-periodic-update",
-                "--python",
-                executable or sys.executable,
+
+        args = [
+            "--no-download",
+            "--no-periodic-update",
+            "--python",
+            executable or sys.executable,
+        ]
+
+        if prompt is not None:
+            args.extend(["--prompt", prompt])
+
+        for flag, value in flags.items():
+            if value is True:
+                args.append(f"--{flag}")
+
+        args.append(str(path))
+
+        cli_result = virtualenv.cli_run(args)
+
+        # Exclude the venv folder from from macOS Time Machine backups
+        # TODO: Add backup-ignore markers for other platforms too
+        if sys.platform == "darwin":
+            import xattr
+
+            xattr.setxattr(
                 str(path),
-            ]
-        )
+                "com.apple.metadata:com_apple_backup_excludeItem",
+                plistlib.dumps("com.apple.backupd", fmt=plistlib.FMT_BINARY),
+            )
+
+        return cli_result
 
     @classmethod
-    def remove_venv(cls, path):  # type: (Union[Path,str]) -> None
+    def remove_venv(cls, path: Path | str) -> None:
         if isinstance(path, str):
             path = Path(path)
         assert path.is_dir()
         try:
-            shutil.rmtree(str(path))
+            remove_directory(path)
             return
         except OSError as e:
             # Continue only if e.errno == 16
@@ -863,16 +1160,16 @@ def remove_venv(cls, path):  # type: (Union[Path,str]) -> None
             if file_path.is_file() or file_path.is_symlink():
                 file_path.unlink()
             elif file_path.is_dir():
-                shutil.rmtree(str(file_path))
+                remove_directory(file_path, force=True)
 
     @classmethod
-    def get_system_env(
-        cls, naive=False
-    ):  # type: (bool) -> Union["SystemEnv", "GenericEnv"]
+    def get_system_env(cls, naive: bool = False) -> Env:
         """
         Retrieve the current Python environment.
+
         This can be the base Python environment or an activated virtual environment.
-        This method also works around the issue that the virtual environment
+
+        This method also workaround the issue that the virtual environment
         used by Poetry internally (when installed via the custom installer)
         is incorrectly detected as the system environment. Note that this workaround
         happens only when `naive` is False since there are times where we actually
@@ -880,7 +1177,7 @@ def get_system_env(
         (e.g. plugin installation or self update).
         """
         prefix, base_prefix = Path(sys.prefix), Path(cls.get_base_prefix())
-        env = SystemEnv(prefix)
+        env: Env = SystemEnv(prefix)
         if not naive:
             if prefix.joinpath("poetry_env").exists():
                 env = GenericEnv(base_prefix, child_env=env)
@@ -897,144 +1194,211 @@ def get_system_env(
         return env
 
     @classmethod
-    def get_base_prefix(cls):  # type: () -> str
-        if hasattr(sys, "real_prefix"):
-            return sys.real_prefix
+    def get_base_prefix(cls) -> Path:
+        real_prefix = getattr(sys, "real_prefix", None)
+        if real_prefix is not None:
+            return Path(real_prefix)
 
-        if hasattr(sys, "base_prefix"):
-            return sys.base_prefix
+        base_prefix = getattr(sys, "base_prefix", None)
+        if base_prefix is not None:
+            return Path(base_prefix)
 
-        return sys.prefix
+        return Path(sys.prefix)
 
     @classmethod
-    def generate_env_name(cls, name, cwd):  # type: (str, str) -> str
+    def generate_env_name(cls, name: str, cwd: str) -> str:
         name = name.lower()
         sanitized_name = re.sub(r'[ $`!*@"\\\r\n\t]', "_", name)[:42]
-        h = hashlib.sha256(encode(cwd)).digest()
-        h = base64.urlsafe_b64encode(h).decode()[:8]
+        normalized_cwd = os.path.normcase(os.path.realpath(cwd))
+        h_bytes = hashlib.sha256(encode(normalized_cwd)).digest()
+        h_str = base64.urlsafe_b64encode(h_bytes).decode()[:8]
 
-        return "{}-{}".format(sanitized_name, h)
+        return f"{sanitized_name}-{h_str}"
 
 
-class Env(object):
+class Env:
     """
     An abstract Python environment.
     """
 
-    def __init__(self, path, base=None):  # type: (Path, Optional[Path]) -> None
+    def __init__(self, path: Path, base: Path | None = None) -> None:
         self._is_windows = sys.platform == "win32"
         self._is_mingw = sysconfig.get_platform().startswith("mingw")
         self._is_conda = bool(os.environ.get("CONDA_DEFAULT_ENV"))
 
+        if self._is_windows:
+            path = get_real_windows_path(path)
+            base = get_real_windows_path(base) if base else None
+
         if not self._is_windows or self._is_mingw:
             bin_dir = "bin"
         else:
             bin_dir = "Scripts"
-
         self._path = path
         self._bin_dir = self._path / bin_dir
 
-        self._base = base or path
-
         self._executable = "python"
         self._pip_executable = "pip"
 
         self.find_executables()
 
-        self._marker_env = None
-        self._pip_version = None
-        self._site_packages = None
-        self._paths = None
-        self._supported_tags = None
-        self._purelib = None
-        self._platlib = None
-        self._script_dirs = None
+        self._base = base or path
+
+        self._marker_env: dict[str, Any] | None = None
+        self._pip_version: Version | None = None
+        self._site_packages: SitePackages | None = None
+        self._paths: dict[str, str] | None = None
+        self._supported_tags: list[Tag] | None = None
+        self._purelib: Path | None = None
+        self._platlib: Path | None = None
+        self._script_dirs: list[Path] | None = None
+
+        self._embedded_pip_path: str | None = None
 
     @property
-    def path(self):  # type: () -> Path
+    def path(self) -> Path:
         return self._path
 
     @property
-    def base(self):  # type: () -> Path
+    def base(self) -> Path:
         return self._base
 
     @property
-    def version_info(self):  # type: () -> Tuple[int]
+    def version_info(self) -> tuple[Any, ...]:
         return tuple(self.marker_env["version_info"])
 
     @property
-    def python_implementation(self):  # type: () -> str
-        return self.marker_env["platform_python_implementation"]
+    def python_implementation(self) -> str:
+        implementation: str = self.marker_env["platform_python_implementation"]
+        return implementation
 
     @property
-    def python(self):  # type: () -> str
+    def python(self) -> str:
         """
         Path to current python executable
         """
         return self._bin(self._executable)
 
     @property
-    def marker_env(self):
+    def marker_env(self) -> dict[str, Any]:
         if self._marker_env is None:
             self._marker_env = self.get_marker_env()
 
         return self._marker_env
 
     @property
-    def parent_env(self):  # type: () -> GenericEnv
+    def parent_env(self) -> GenericEnv:
         return GenericEnv(self.base, child_env=self)
 
+    def _find_python_executable(self) -> None:
+        bin_dir = self._bin_dir
+
+        if self._is_windows and self._is_conda:
+            bin_dir = self._path
+
+        python_executables = sorted(
+            p.name
+            for p in bin_dir.glob("python*")
+            if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
+        )
+        if python_executables:
+            executable = python_executables[0]
+            if executable.endswith(".exe"):
+                executable = executable[:-4]
+
+            self._executable = executable
+
+    def _find_pip_executable(self) -> None:
+        pip_executables = sorted(
+            p.name
+            for p in self._bin_dir.glob("pip*")
+            if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
+        )
+        if pip_executables:
+            pip_executable = pip_executables[0]
+            if pip_executable.endswith(".exe"):
+                pip_executable = pip_executable[:-4]
+
+            self._pip_executable = pip_executable
+
+    def find_executables(self) -> None:
+        self._find_python_executable()
+        self._find_pip_executable()
+
+    def get_embedded_wheel(self, distribution: str) -> Path:
+        wheel: Wheel = get_embed_wheel(
+            distribution, f"{self.version_info[0]}.{self.version_info[1]}"
+        )
+        path: Path = wheel.path
+        return path
+
+    @property
+    def pip_embedded(self) -> str:
+        if self._embedded_pip_path is None:
+            self._embedded_pip_path = str(self.get_embedded_wheel("pip") / "pip")
+        return self._embedded_pip_path
+
     @property
-    def pip(self):  # type: () -> str
+    def pip(self) -> str:
         """
         Path to current pip executable
         """
-        return self._bin(self._pip_executable)
+        # we do not use as_posix() here due to issues with windows pathlib2
+        # implementation
+        path = self._bin(self._pip_executable)
+        if not Path(path).exists():
+            return str(self.pip_embedded)
+        return path
 
     @property
-    def platform(self):  # type: () -> str
+    def platform(self) -> str:
         return sys.platform
 
     @property
-    def os(self):  # type: () -> str
+    def os(self) -> str:
         return os.name
 
     @property
-    def pip_version(self):
+    def pip_version(self) -> Version:
         if self._pip_version is None:
             self._pip_version = self.get_pip_version()
 
         return self._pip_version
 
     @property
-    def site_packages(self):  # type: () -> SitePackages
+    def site_packages(self) -> SitePackages:
         if self._site_packages is None:
             # we disable write checks if no user site exist
             fallbacks = [self.usersite] if self.usersite else []
             self._site_packages = SitePackages(
-                self.purelib, fallbacks, skip_write_checks=False if fallbacks else True
+                self.purelib,
+                self.platlib,
+                fallbacks,
+                skip_write_checks=not fallbacks,
             )
         return self._site_packages
 
     @property
-    def usersite(self):  # type: () -> Optional[Path]
+    def usersite(self) -> Path | None:
         if "usersite" in self.paths:
             return Path(self.paths["usersite"])
+        return None
 
     @property
-    def userbase(self):  # type: () -> Optional[Path]
+    def userbase(self) -> Path | None:
         if "userbase" in self.paths:
             return Path(self.paths["userbase"])
+        return None
 
     @property
-    def purelib(self):  # type: () -> Path
+    def purelib(self) -> Path:
         if self._purelib is None:
             self._purelib = Path(self.paths["purelib"])
 
         return self._purelib
 
     @property
-    def platlib(self):  # type: () -> Path
+    def platlib(self) -> Path:
         if self._platlib is None:
             if "platlib" in self.paths:
                 self._platlib = Path(self.paths["platlib"])
@@ -1043,187 +1407,167 @@ def platlib(self):  # type: () -> Path
 
         return self._platlib
 
-    def is_path_relative_to_lib(self, path):  # type: (Path) -> bool
+    def is_path_relative_to_lib(self, path: Path) -> bool:
         for lib_path in [self.purelib, self.platlib]:
-            try:
+            with contextlib.suppress(ValueError):
                 path.relative_to(lib_path)
                 return True
-            except ValueError:
-                pass
 
         return False
 
     @property
-    def sys_path(self):  # type: () -> List[str]
+    def sys_path(self) -> list[str]:
         raise NotImplementedError()
 
     @property
-    def paths(self):  # type: () -> Dict[str, str]
+    def paths(self) -> dict[str, str]:
         if self._paths is None:
             self._paths = self.get_paths()
 
         return self._paths
 
     @property
-    def supported_tags(self):  # type: () -> List[Tag]
+    def supported_tags(self) -> list[Tag]:
         if self._supported_tags is None:
             self._supported_tags = self.get_supported_tags()
 
         return self._supported_tags
 
     @classmethod
-    def get_base_prefix(cls):  # type: () -> str
-        if hasattr(sys, "real_prefix"):
-            return sys.real_prefix
-
-        if hasattr(sys, "base_prefix"):
-            return sys.base_prefix
-
-        return sys.prefix
-
-    def _find_python_executable(self):  # type: () -> None
-        bin_dir = self._bin_dir
-
-        if self._is_windows and self._is_conda:
-            bin_dir = self._path
-
-        python_executables = sorted(
-            p.name
-            for p in bin_dir.glob("python*")
-            if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
-        )
-        if python_executables:
-            executable = python_executables[0]
-            if executable.endswith(".exe"):
-                executable = executable[:-4]
+    def get_base_prefix(cls) -> Path:
+        real_prefix = getattr(sys, "real_prefix", None)
+        if real_prefix is not None:
+            return Path(real_prefix)
 
-            self._executable = executable
+        base_prefix = getattr(sys, "base_prefix", None)
+        if base_prefix is not None:
+            return Path(base_prefix)
 
-    def _find_pip_executable(self):  # type: () -> None
-        pip_executables = sorted(
-            p.name
-            for p in self._bin_dir.glob("pip*")
-            if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
-        )
-        if pip_executables:
-            pip_executable = pip_executables[0]
-            if pip_executable.endswith(".exe"):
-                pip_executable = pip_executable[:-4]
+        return Path(sys.prefix)
 
-            self._pip_executable = pip_executable
-
-    def find_executables(self):  # type: () -> None
-        self._find_python_executable()
-        self._find_pip_executable()
-
-    def get_version_info(self):  # type: () -> Tuple[int]
+    def get_version_info(self) -> tuple[Any, ...]:
         raise NotImplementedError()
 
-    def get_python_implementation(self):  # type: () -> str
+    def get_python_implementation(self) -> str:
         raise NotImplementedError()
 
-    def get_marker_env(self):  # type: () -> Dict[str, Any]
+    def get_marker_env(self) -> dict[str, Any]:
         raise NotImplementedError()
 
-    def get_pip_command(self):  # type: () -> List[str]
-        raise NotImplementedError()
+    def get_pip_command(self, embedded: bool = False) -> list[str]:
+        if embedded or not Path(self._bin(self._pip_executable)).exists():
+            return [self.python, self.pip_embedded]
+        # run as module so that pip can update itself on Windows
+        return [self.python, "-m", "pip"]
 
-    def get_supported_tags(self):  # type: () -> List[Tag]
+    def get_supported_tags(self) -> list[Tag]:
         raise NotImplementedError()
 
-    def get_pip_version(self):  # type: () -> Version
+    def get_pip_version(self) -> Version:
         raise NotImplementedError()
 
-    def get_paths(self):  # type: () -> Dict[str, str]
+    def get_paths(self) -> dict[str, str]:
         raise NotImplementedError()
 
-    def is_valid_for_marker(self, marker):  # type: (BaseMarker) -> bool
-        return marker.validate(self.marker_env)
+    def is_valid_for_marker(self, marker: BaseMarker) -> bool:
+        valid: bool = marker.validate(self.marker_env)
+        return valid
 
-    def is_sane(self):  # type: () -> bool
+    def is_sane(self) -> bool:
         """
         Checks whether the current environment is sane or not.
         """
         return True
 
-    def run(self, bin, *args, **kwargs):
-        bin = self._bin(bin)
-        cmd = [bin] + list(args)
-        return self._run(cmd, **kwargs)
+    def get_command_from_bin(self, bin: str) -> list[str]:
+        if bin == "pip":
+            # when pip is required we need to ensure that we fallback to
+            # embedded pip when pip is not available in the environment
+            return self.get_pip_command()
 
-    def run_python(self, *args, **kwargs):
-        return self.run(self._executable, *args, **kwargs)
+        return [self._bin(bin)]
+
+    def run(self, bin: str, *args: str, **kwargs: Any) -> str | int:
+        cmd = self.get_command_from_bin(bin) + list(args)
+        return self._run(cmd, **kwargs)
 
-    def run_pip(self, *args, **kwargs):
+    def run_pip(self, *args: str, **kwargs: Any) -> int | str:
         pip = self.get_pip_command()
         cmd = pip + list(args)
         return self._run(cmd, **kwargs)
 
-    def run_python_script(self, content, **kwargs):  # type: (str, Any) -> str
-        return self.run(self._executable, "-W", "ignore", "-", input_=content, **kwargs)
+    def run_python_script(self, content: str, **kwargs: Any) -> int | str:
+        return self.run(
+            self._executable, "-I", "-W", "ignore", "-", input_=content, **kwargs
+        )
 
-    def _run(self, cmd, **kwargs):
+    def _run(self, cmd: list[str], **kwargs: Any) -> int | str:
         """
         Run a command inside the Python environment.
         """
         call = kwargs.pop("call", False)
         input_ = kwargs.pop("input_", None)
+        env = kwargs.pop("env", dict(os.environ))
 
         try:
             if self._is_windows:
                 kwargs["shell"] = True
 
+            command: str | list[str]
             if kwargs.get("shell", False):
-                cmd = list_to_shell_command(cmd)
+                command = list_to_shell_command(cmd)
+            else:
+                command = cmd
 
             if input_:
                 output = subprocess.run(
-                    cmd,
+                    command,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     input=encode(input_),
                     check=True,
-                    **kwargs
+                    **kwargs,
                 ).stdout
             elif call:
-                return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs)
+                return subprocess.call(
+                    command, stderr=subprocess.STDOUT, env=env, **kwargs
+                )
             else:
                 output = subprocess.check_output(
-                    cmd, stderr=subprocess.STDOUT, **kwargs
+                    command, stderr=subprocess.STDOUT, env=env, **kwargs
                 )
         except CalledProcessError as e:
             raise EnvCommandError(e, input=input_)
 
         return decode(output)
 
-    def execute(self, bin, *args, **kwargs):
-        bin = self._bin(bin)
-        env = kwargs.pop("env", {k: v for k, v in os.environ.items()})
+    def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
+        command = self.get_command_from_bin(bin) + list(args)
+        env = kwargs.pop("env", dict(os.environ))
 
         if not self._is_windows:
-            args = [bin] + list(args)
-            return os.execvpe(bin, args, env=env)
-        else:
-            exe = subprocess.Popen([bin] + list(args), env=env, **kwargs)
-            exe.communicate()
-            return exe.returncode
+            return os.execvpe(command[0], command, env=env)
+
+        kwargs["shell"] = True
+        exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs)
+        exe.communicate()
+        return exe.returncode
 
-    def is_venv(self):  # type: () -> bool
+    def is_venv(self) -> bool:
         raise NotImplementedError()
 
     @property
-    def script_dirs(self):  # type: () -> List[Path]
+    def script_dirs(self) -> list[Path]:
         if self._script_dirs is None:
-            self._script_dirs = (
-                [Path(self.paths["scripts"])]
-                if "scripts" in self.paths
-                else self._bin_dir
-            )
+            scripts = self.paths.get("scripts")
+            self._script_dirs = [
+                Path(scripts) if scripts is not None else self._bin_dir
+            ]
             if self.userbase:
                 self._script_dirs.append(self.userbase / self._script_dirs[0].name)
         return self._script_dirs
 
-    def _bin(self, bin):  # type: (str) -> str
+    def _bin(self, bin: str) -> str:
         """
         Return path to the given executable.
         """
@@ -1237,10 +1581,6 @@ def _bin(self, bin):  # type: (str) -> str
             # This is especially true when installing Python with
             # the official installer, where python.exe will be at
             # the root of the env path.
-            # This is an edge case and should not be encountered
-            # in normal uses but this happens in the sonnet script
-            # that creates a fake virtual environment pointing to
-            # a base Python install.
             if self._is_windows:
                 if not bin.endswith(".exe"):
                     bin_path = self._path / (bin + ".exe")
@@ -1254,11 +1594,14 @@ def _bin(self, bin):  # type: (str) -> str
 
         return str(bin_path)
 
-    def __eq__(self, other):  # type: (Env) -> bool
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, Env):
+            return False
+
         return other.__class__ == self.__class__ and other.path == self.path
 
-    def __repr__(self):
-        return '{}("{}")'.format(self.__class__.__name__, self._path)
+    def __repr__(self) -> str:
+        return f'{self.__class__.__name__}("{self._path}")'
 
 
 class SystemEnv(Env):
@@ -1267,37 +1610,35 @@ class SystemEnv(Env):
     """
 
     @property
-    def python(self):  # type: () -> str
+    def python(self) -> str:
         return sys.executable
 
     @property
-    def sys_path(self):  # type: () -> List[str]
+    def sys_path(self) -> list[str]:
         return sys.path
 
-    def get_version_info(self):  # type: () -> Tuple[int]
-        return sys.version_info
+    def get_version_info(self) -> tuple[Any, ...]:
+        return tuple(sys.version_info)
 
-    def get_python_implementation(self):  # type: () -> str
+    def get_python_implementation(self) -> str:
         return platform.python_implementation()
 
-    def get_pip_command(self):  # type: () -> List[str]
-        # If we're not in a venv, assume the interpreter we're running on
-        # has a pip and use that
-        return [sys.executable, "-m", "pip"]
-
-    def get_paths(self):  # type: () -> Dict[str, str]
+    def get_paths(self) -> dict[str, str]:
         # We can't use sysconfig.get_paths() because
         # on some distributions it does not return the proper paths
         # (those used by pip for instance). We go through distutils
         # to get the proper ones.
         import site
 
-        from distutils.command.install import SCHEME_KEYS  # noqa
+        from distutils.command.install import SCHEME_KEYS
         from distutils.core import Distribution
 
         d = Distribution()
         d.parse_config_files()
-        obj = d.get_command_obj("install", create=True)
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", "setup.py install is deprecated")
+            obj = d.get_command_obj("install", create=True)
+        assert obj is not None
         obj.finalize_options()
 
         paths = sysconfig.get_paths().copy()
@@ -1306,21 +1647,24 @@ def get_paths(self):  # type: () -> Dict[str, str]
                 # headers is not a path returned by sysconfig.get_paths()
                 continue
 
-            paths[key] = getattr(obj, "install_{}".format(key))
+            paths[key] = getattr(obj, f"install_{key}")
 
-        if site.check_enableusersite() and hasattr(obj, "install_usersite"):
-            paths["usersite"] = getattr(obj, "install_usersite")
-            paths["userbase"] = getattr(obj, "install_userbase")
+        if site.check_enableusersite():
+            usersite = getattr(obj, "install_usersite", None)
+            userbase = getattr(obj, "install_userbase", None)
+            if usersite is not None and userbase is not None:
+                paths["usersite"] = usersite
+                paths["userbase"] = userbase
 
         return paths
 
-    def get_supported_tags(self):  # type: () -> List[Tag]
+    def get_supported_tags(self) -> list[Tag]:
         return list(sys_tags())
 
-    def get_marker_env(self):  # type: () -> Dict[str, Any]
+    def get_marker_env(self) -> dict[str, Any]:
         if hasattr(sys, "implementation"):
             info = sys.implementation.version
-            iver = "{0.major}.{0.minor}.{0.micro}".format(info)
+            iver = f"{info.major}.{info.minor}.{info.micro}"
             kind = info.releaselevel
             if kind != "final":
                 iver += kind[0] + str(info.serial)
@@ -1340,22 +1684,19 @@ def get_marker_env(self):  # type: () -> Dict[str, Any]
             "platform_version": platform.version(),
             "python_full_version": platform.python_version(),
             "platform_python_implementation": platform.python_implementation(),
-            "python_version": ".".join(
-                v for v in platform.python_version().split(".")[:2]
-            ),
+            "python_version": ".".join(platform.python_version().split(".")[:2]),
             "sys_platform": sys.platform,
             "version_info": sys.version_info,
-            # Extra information
             "interpreter_name": interpreter_name(),
             "interpreter_version": interpreter_version(),
         }
 
-    def get_pip_version(self):  # type: () -> Version
+    def get_pip_version(self) -> Version:
         from pip import __version__
 
         return Version.parse(__version__)
 
-    def is_venv(self):  # type: () -> bool
+    def is_venv(self) -> bool:
         return self._path != self._base
 
 
@@ -1364,97 +1705,82 @@ class VirtualEnv(Env):
     A virtual Python environment.
     """
 
-    def __init__(self, path, base=None):  # type: (Path, Optional[Path]) -> None
-        super(VirtualEnv, self).__init__(path, base)
+    def __init__(self, path: Path, base: Path | None = None) -> None:
+        super().__init__(path, base)
 
         # If base is None, it probably means this is
         # a virtualenv created from VIRTUAL_ENV.
         # In this case we need to get sys.base_prefix
         # from inside the virtualenv.
         if base is None:
-            self._base = Path(self.run_python_script(GET_BASE_PREFIX).strip())
+            output = self.run_python_script(GET_BASE_PREFIX)
+            assert isinstance(output, str)
+            self._base = Path(output.strip())
 
     @property
-    def sys_path(self):  # type: () -> List[str]
+    def sys_path(self) -> list[str]:
         output = self.run_python_script(GET_SYS_PATH)
+        assert isinstance(output, str)
+        paths: list[str] = json.loads(output)
+        return paths
 
-        return json.loads(output)
-
-    def get_version_info(self):  # type: () -> Tuple[int]
+    def get_version_info(self) -> tuple[Any, ...]:
         output = self.run_python_script(GET_PYTHON_VERSION)
+        assert isinstance(output, str)
 
-        return tuple([int(s) for s in output.strip().split(".")])
-
-    def get_python_implementation(self):  # type: () -> str
-        return self.marker_env["platform_python_implementation"]
-
-    def get_pip_command(self):  # type: () -> List[str]
-        # We're in a virtualenv that is known to be sane,
-        # so assume that we have a functional pip
-        return [self._bin(self._pip_executable)]
-
-    def get_supported_tags(self):  # type: () -> List[Tag]
-        file_path = Path(packaging.tags.__file__)
-        if file_path.suffix == ".pyc":
-            # Python 2
-            file_path = file_path.with_suffix(".py")
-
-        with file_path.open(encoding="utf-8") as f:
-            script = decode(f.read())
-
-        script = script.replace(
-            "from ._typing import TYPE_CHECKING, cast",
-            "TYPE_CHECKING = False\ncast = lambda type_, value: value",
-        )
-        script = script.replace(
-            "from ._typing import MYPY_CHECK_RUNNING, cast",
-            "MYPY_CHECK_RUNNING = False\ncast = lambda type_, value: value",
-        )
-
-        script += textwrap.dedent(
-            """
-            import json
+        return tuple(int(s) for s in output.strip().split("."))
 
-            print(json.dumps([(t.interpreter, t.abi, t.platform) for t in sys_tags()]))
-            """
-        )
+    def get_python_implementation(self) -> str:
+        implementation: str = self.marker_env["platform_python_implementation"]
+        return implementation
 
-        output = self.run_python_script(script)
+    def get_supported_tags(self) -> list[Tag]:
+        output = self.run_python_script(GET_SYS_TAGS)
+        assert isinstance(output, str)
 
         return [Tag(*t) for t in json.loads(output)]
 
-    def get_marker_env(self):  # type: () -> Dict[str, Any]
-        output = self.run(self._executable, "-", input_=GET_ENVIRONMENT_INFO)
+    def get_marker_env(self) -> dict[str, Any]:
+        output = self.run_python_script(GET_ENVIRONMENT_INFO)
+        assert isinstance(output, str)
+
+        env: dict[str, Any] = json.loads(output)
+        return env
 
-        return json.loads(output)
+    def get_pip_version(self) -> Version:
+        output = self.run_pip("--version")
+        assert isinstance(output, str)
+        output = output.strip()
 
-    def get_pip_version(self):  # type: () -> Version
-        output = self.run_pip("--version").strip()
         m = re.match("pip (.+?)(?: from .+)?$", output)
         if not m:
             return Version.parse("0.0")
 
         return Version.parse(m.group(1))
 
-    def get_paths(self):  # type: () -> Dict[str, str]
+    def get_paths(self) -> dict[str, str]:
         output = self.run_python_script(GET_PATHS)
+        assert isinstance(output, str)
+        paths: dict[str, str] = json.loads(output)
+        return paths
 
-        return json.loads(output)
-
-    def is_venv(self):  # type: () -> bool
+    def is_venv(self) -> bool:
         return True
 
-    def is_sane(self):
-        # A virtualenv is considered sane if both "python" and "pip" exist.
-        return os.path.exists(self.python) and os.path.exists(self._bin("pip"))
+    def is_sane(self) -> bool:
+        # A virtualenv is considered sane if "python" exists.
+        return os.path.exists(self.python)
 
-    def _run(self, cmd, **kwargs):
+    def _run(self, cmd: list[str], **kwargs: Any) -> int | str:
         kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env"))
-        return super(VirtualEnv, self)._run(cmd, **kwargs)
+        return super()._run(cmd, **kwargs)
 
     def get_temp_environ(
-        self, environ=None, exclude=None, **kwargs
-    ):  # type: (Optional[Dict[str, str]], Optional[List[str]], **str) -> Dict[str, str]
+        self,
+        environ: dict[str, str] | None = None,
+        exclude: list[str] | None = None,
+        **kwargs: str,
+    ) -> dict[str, str]:
         exclude = exclude or []
         exclude.extend(["PYTHONHOME", "__PYVENV_LAUNCHER__"])
 
@@ -1472,12 +1798,12 @@ def get_temp_environ(
 
         return environ
 
-    def execute(self, bin, *args, **kwargs):
+    def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
         kwargs["env"] = self.get_temp_environ(environ=kwargs.get("env"))
-        return super(VirtualEnv, self).execute(bin, *args, **kwargs)
+        return super().execute(bin, *args, **kwargs)
 
     @contextmanager
-    def temp_environ(self):
+    def temp_environ(self) -> Iterator[None]:
         environ = dict(os.environ)
         try:
             yield
@@ -1485,29 +1811,29 @@ def temp_environ(self):
             os.environ.clear()
             os.environ.update(environ)
 
-    def _updated_path(self):
+    def _updated_path(self) -> str:
         return os.pathsep.join([str(self._bin_dir), os.environ.get("PATH", "")])
 
 
 class GenericEnv(VirtualEnv):
     def __init__(
-        self, path, base=None, child_env=None
-    ):  # type: (Path, Optional[Path], Optional[Env]) -> None
+        self, path: Path, base: Path | None = None, child_env: Env | None = None
+    ) -> None:
         self._child_env = child_env
 
-        super(GenericEnv, self).__init__(path, base=base)
+        super().__init__(path, base=base)
 
-    def find_executables(self):  # type: () -> None
+    def find_executables(self) -> None:
         patterns = [("python*", "pip*")]
 
         if self._child_env:
-            minor_version = "{}.{}".format(
-                self._child_env.version_info[0], self._child_env.version_info[1]
+            minor_version = (
+                f"{self._child_env.version_info[0]}.{self._child_env.version_info[1]}"
             )
-            major_version = "{}".format(self._child_env.version_info[0])
+            major_version = f"{self._child_env.version_info[0]}"
             patterns = [
-                ("python{}".format(minor_version), "pip{}".format(minor_version)),
-                ("python{}".format(major_version), "pip{}".format(major_version)),
+                (f"python{minor_version}", f"pip{minor_version}"),
+                (f"python{major_version}", f"pip{major_version}"),
             ]
 
         python_executable = None
@@ -1519,11 +1845,9 @@ def find_executables(self):  # type: () -> None
 
             if not python_executable:
                 python_executables = sorted(
-                    [
-                        p.name
-                        for p in self._bin_dir.glob(python_pattern)
-                        if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
-                    ]
+                    p.name
+                    for p in self._bin_dir.glob(python_pattern)
+                    if re.match(r"python(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
                 )
 
                 if python_executables:
@@ -1535,119 +1859,202 @@ def find_executables(self):  # type: () -> None
 
             if not pip_executable:
                 pip_executables = sorted(
-                    [
-                        p.name
-                        for p in self._bin_dir.glob(pip_pattern)
-                        if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
-                    ]
+                    p.name
+                    for p in self._bin_dir.glob(pip_pattern)
+                    if re.match(r"pip(?:\d+(?:\.\d+)?)?(?:\.exe)?$", p.name)
                 )
                 if pip_executables:
                     pip_executable = pip_executables[0]
                     if pip_executable.endswith(".exe"):
                         pip_executable = pip_executable[:-4]
 
-                    pip_executable = pip_executable
-
             if python_executable:
                 self._executable = python_executable
 
             if pip_executable:
                 self._pip_executable = pip_executable
 
-    def get_paths(self):  # type: () -> Dict[str, str]
+    def get_paths(self) -> dict[str, str]:
         output = self.run_python_script(GET_PATHS_FOR_GENERIC_ENVS)
+        assert isinstance(output, str)
 
-        return json.loads(output)
+        paths: dict[str, str] = json.loads(output)
+        return paths
 
-    def execute(self, bin, *args, **kwargs):  # type: (str, str, Any) -> Optional[int]
-        return super(VirtualEnv, self).execute(bin, *args, **kwargs)
+    def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
+        command = self.get_command_from_bin(bin) + list(args)
+        env = kwargs.pop("env", dict(os.environ))
 
-    def _run(self, cmd, **kwargs):  # type: (List[str], Any) -> Optional[int]
+        if not self._is_windows:
+            return os.execvpe(command[0], command, env=env)
+
+        exe = subprocess.Popen([command[0]] + command[1:], env=env, **kwargs)
+        exe.communicate()
+
+        return exe.returncode
+
+    def _run(self, cmd: list[str], **kwargs: Any) -> int | str:
         return super(VirtualEnv, self)._run(cmd, **kwargs)
 
-    def is_venv(self):  # type: () -> bool
+    def is_venv(self) -> bool:
         return self._path != self._base
 
 
 class NullEnv(SystemEnv):
-    def __init__(self, path=None, base=None, execute=False):
+    def __init__(
+        self, path: Path | None = None, base: Path | None = None, execute: bool = False
+    ) -> None:
         if path is None:
             path = Path(sys.prefix)
 
-        super(NullEnv, self).__init__(path, base=base)
+        super().__init__(path, base=base)
 
         self._execute = execute
-        self.executed = []
-
-    def get_pip_command(self):  # type: () -> List[str]
-        return [self._bin("python"), "-m", "pip"]
+        self.executed: list[list[str]] = []
 
-    def _run(self, cmd, **kwargs):
+    def _run(self, cmd: list[str], **kwargs: Any) -> int | str:
         self.executed.append(cmd)
 
         if self._execute:
-            return super(NullEnv, self)._run(cmd, **kwargs)
+            return super()._run(cmd, **kwargs)
+        return 0
 
-    def execute(self, bin, *args, **kwargs):
+    def execute(self, bin: str, *args: str, **kwargs: Any) -> int:
         self.executed.append([bin] + list(args))
 
         if self._execute:
-            return super(NullEnv, self).execute(bin, *args, **kwargs)
+            return super().execute(bin, *args, **kwargs)
+        return 0
 
-    def _bin(self, bin):
+    def _bin(self, bin: str) -> str:
         return bin
 
 
+@contextmanager
+def ephemeral_environment(
+    executable: str | Path | None = None,
+    flags: dict[str, bool] | None = None,
+) -> Iterator[VirtualEnv]:
+    with temporary_directory() as tmp_dir:
+        # TODO: cache PEP 517 build environment corresponding to each project venv
+        venv_dir = Path(tmp_dir) / ".venv"
+        EnvManager.build_venv(
+            path=venv_dir.as_posix(),
+            executable=executable,
+            flags=flags,
+        )
+        yield VirtualEnv(venv_dir, venv_dir)
+
+
+@contextmanager
+def build_environment(
+    poetry: CorePoetry, env: Env | None = None, io: IO | None = None
+) -> Iterator[Env]:
+    """
+    If a build script is specified for the project, there could be additional build
+    time dependencies, eg: cython, setuptools etc. In these cases, we create an
+    ephemeral build environment with all requirements specified under
+    `build-system.requires` and return this. Otherwise, the given default project
+    environment is returned.
+    """
+    if not env or poetry.package.build_script:
+        with ephemeral_environment(executable=env.python if env else None) as venv:
+            overwrite = (
+                io is not None and io.output.is_decorated() and not io.is_debug()
+            )
+
+            if io:
+                if not overwrite:
+                    io.write_error_line("")
+
+                requires = [
+                    f"{requirement}"
+                    for requirement in poetry.pyproject.build_system.requires
+                ]
+
+                io.overwrite_error(
+                    "Preparing build environment with build-system requirements"
+                    f" {', '.join(requires)}"
+                )
+
+            venv.run_pip(
+                "install",
+                "--disable-pip-version-check",
+                "--ignore-installed",
+                "--no-input",
+                *poetry.pyproject.build_system.requires,
+            )
+
+            if overwrite:
+                assert io is not None
+                io.write_error_line("")
+
+            yield venv
+    else:
+        yield env
+
+
 class MockEnv(NullEnv):
     def __init__(
         self,
-        version_info=(3, 7, 0),
-        python_implementation="CPython",
-        platform="darwin",
-        os_name="posix",
-        is_venv=False,
-        pip_version="19.1",
-        sys_path=None,
-        marker_env=None,
-        supported_tags=None,
-        **kwargs
-    ):
-        super(MockEnv, self).__init__(**kwargs)
+        version_info: tuple[int, int, int] = (3, 7, 0),
+        python_implementation: str = "CPython",
+        platform: str = "darwin",
+        os_name: str = "posix",
+        is_venv: bool = False,
+        pip_version: str = "19.1",
+        sys_path: list[str] | None = None,
+        marker_env: dict[str, Any] | None = None,
+        supported_tags: list[Tag] | None = None,
+        **kwargs: Any,
+    ) -> None:
+        super().__init__(**kwargs)
 
         self._version_info = version_info
         self._python_implementation = python_implementation
         self._platform = platform
         self._os_name = os_name
         self._is_venv = is_venv
-        self._pip_version = Version.parse(pip_version)
+        self._pip_version: Version = Version.parse(pip_version)
         self._sys_path = sys_path
         self._mock_marker_env = marker_env
         self._supported_tags = supported_tags
 
     @property
-    def platform(self):  # type: () -> str
+    def platform(self) -> str:
         return self._platform
 
     @property
-    def os(self):  # type: () -> str
+    def os(self) -> str:
         return self._os_name
 
     @property
-    def pip_version(self):
+    def pip_version(self) -> Version:
         return self._pip_version
 
     @property
-    def sys_path(self):
+    def sys_path(self) -> list[str]:
         if self._sys_path is None:
-            return super(MockEnv, self).sys_path
+            return super().sys_path
 
         return self._sys_path
 
-    def get_marker_env(self):  # type: () -> Dict[str, Any]
+    @property
+    def paths(self) -> dict[str, str]:
+        if self._paths is None:
+            self._paths = self.get_paths()
+            self._paths["platlib"] = str(self._path / "platlib")
+            self._paths["purelib"] = str(self._path / "purelib")
+            self._paths["scripts"] = str(self._path / "scripts")
+            self._paths["data"] = str(self._path / "data")
+
+        return self._paths
+
+    def get_marker_env(self) -> dict[str, Any]:
         if self._mock_marker_env is not None:
             return self._mock_marker_env
 
-        marker_env = super(MockEnv, self).get_marker_env()
+        marker_env = super().get_marker_env()
         marker_env["python_implementation"] = self._python_implementation
         marker_env["version_info"] = self._version_info
         marker_env["python_version"] = ".".join(str(v) for v in self._version_info[:2])
@@ -1660,5 +2067,5 @@ def get_marker_env(self):  # type: () -> Dict[str, Any]
 
         return marker_env
 
-    def is_venv(self):  # type: () -> bool
+    def is_venv(self) -> bool:
         return self._is_venv
diff --git a/conda_lock/_vendor/poetry/utils/exporter.py b/conda_lock/_vendor/poetry/utils/exporter.py
deleted file mode 100644
index 1554d716c..000000000
--- a/conda_lock/_vendor/poetry/utils/exporter.py
+++ /dev/null
@@ -1,169 +0,0 @@
-from typing import Optional
-from typing import Sequence
-from typing import Union
-
-from clikit.api.io import IO
-
-from conda_lock._vendor.poetry.core.packages.utils.utils import path_to_url
-from conda_lock._vendor.poetry.poetry import Poetry
-from conda_lock._vendor.poetry.utils._compat import Path
-from conda_lock._vendor.poetry.utils._compat import decode
-from conda_lock._vendor.poetry.utils._compat import urlparse
-
-
-class Exporter(object):
-    """
-    Exporter class to export a lock file to alternative formats.
-    """
-
-    FORMAT_REQUIREMENTS_TXT = "requirements.txt"
-    #: The names of the supported export formats.
-    ACCEPTED_FORMATS = (FORMAT_REQUIREMENTS_TXT,)
-    ALLOWED_HASH_ALGORITHMS = ("sha256", "sha384", "sha512")
-
-    def __init__(self, poetry):  # type: (Poetry) -> None
-        self._poetry = poetry
-
-    def export(
-        self,
-        fmt,
-        cwd,
-        output,
-        with_hashes=True,
-        dev=False,
-        extras=None,
-        with_credentials=False,
-    ):  # type: (str, Path, Union[IO, str], bool, bool, Optional[Union[bool, Sequence[str]]], bool) -> None
-        if fmt not in self.ACCEPTED_FORMATS:
-            raise ValueError("Invalid export format: {}".format(fmt))
-
-        getattr(self, "_export_{}".format(fmt.replace(".", "_")))(
-            cwd,
-            output,
-            with_hashes=with_hashes,
-            dev=dev,
-            extras=extras,
-            with_credentials=with_credentials,
-        )
-
-    def _export_requirements_txt(
-        self,
-        cwd,
-        output,
-        with_hashes=True,
-        dev=False,
-        extras=None,
-        with_credentials=False,
-    ):  # type: (Path, Union[IO, str], bool, bool, Optional[Union[bool, Sequence[str]]], bool) -> None
-        indexes = set()
-        content = ""
-        dependency_lines = set()
-
-        for dependency_package in self._poetry.locker.get_project_dependency_packages(
-            project_requires=self._poetry.package.all_requires, dev=dev, extras=extras
-        ):
-            line = ""
-
-            dependency = dependency_package.dependency
-            package = dependency_package.package
-
-            if package.develop:
-                line += "-e "
-
-            requirement = dependency.to_pep_508(with_extras=False)
-            is_direct_local_reference = (
-                dependency.is_file() or dependency.is_directory()
-            )
-            is_direct_remote_reference = dependency.is_vcs() or dependency.is_url()
-
-            if is_direct_remote_reference:
-                line = requirement
-            elif is_direct_local_reference:
-                dependency_uri = path_to_url(dependency.source_url)
-                line = "{} @ {}".format(dependency.name, dependency_uri)
-            else:
-                line = "{}=={}".format(package.name, package.version)
-
-            if not is_direct_remote_reference:
-                if ";" in requirement:
-                    markers = requirement.split(";", 1)[1].strip()
-                    if markers:
-                        line += "; {}".format(markers)
-
-            if (
-                not is_direct_remote_reference
-                and not is_direct_local_reference
-                and package.source_url
-            ):
-                indexes.add(package.source_url)
-
-            if package.files and with_hashes:
-                hashes = []
-                for f in package.files:
-                    h = f["hash"]
-                    algorithm = "sha256"
-                    if ":" in h:
-                        algorithm, h = h.split(":")
-
-                        if algorithm not in self.ALLOWED_HASH_ALGORITHMS:
-                            continue
-
-                    hashes.append("{}:{}".format(algorithm, h))
-
-                if hashes:
-                    line += " \\\n"
-                    for i, h in enumerate(hashes):
-                        line += "    --hash={}{}".format(
-                            h, " \\\n" if i < len(hashes) - 1 else ""
-                        )
-            dependency_lines.add(line)
-
-        content += "\n".join(sorted(dependency_lines))
-        content += "\n"
-
-        if indexes:
-            # If we have extra indexes, we add them to the beginning of the output
-            indexes_header = ""
-            for index in sorted(indexes):
-                repositories = [
-                    r
-                    for r in self._poetry.pool.repositories
-                    if r.url == index.rstrip("/")
-                ]
-                if not repositories:
-                    continue
-                repository = repositories[0]
-                if (
-                    self._poetry.pool.has_default()
-                    and repository is self._poetry.pool.repositories[0]
-                ):
-                    url = (
-                        repository.authenticated_url
-                        if with_credentials
-                        else repository.url
-                    )
-                    indexes_header = "--index-url {}\n".format(url)
-                    continue
-
-                url = (
-                    repository.authenticated_url if with_credentials else repository.url
-                )
-                parsed_url = urlparse.urlsplit(url)
-                if parsed_url.scheme == "http":
-                    indexes_header += "--trusted-host {}\n".format(parsed_url.netloc)
-                indexes_header += "--extra-index-url {}\n".format(url)
-
-            content = indexes_header + "\n" + content
-
-        self._output(content, cwd, output)
-
-    def _output(
-        self, content, cwd, output
-    ):  # type: (str, Path, Union[IO, str]) -> None
-        decoded = decode(content)
-        try:
-            output.write(decoded)
-        except AttributeError:
-            filepath = cwd / output
-            with filepath.open("w", encoding="utf-8") as f:
-                f.write(decoded)
diff --git a/conda_lock/_vendor/poetry/utils/extras.py b/conda_lock/_vendor/poetry/utils/extras.py
index 6dca56eb5..e4ca1b0eb 100644
--- a/conda_lock/_vendor/poetry/utils/extras.py
+++ b/conda_lock/_vendor/poetry/utils/extras.py
@@ -1,17 +1,22 @@
-from typing import Iterator
-from typing import List
-from typing import Mapping
-from typing import Sequence
+from __future__ import annotations
 
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.utils.helpers import canonicalize_name
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from collections.abc import Collection
+    from collections.abc import Iterable
+    from typing import Mapping
+
+    from packaging.utils import NormalizedName
+    from conda_lock._vendor.poetry.core.packages.package import Package
 
 
 def get_extra_package_names(
-    packages,  # type: Sequence[Package]
-    extras,  # type: Mapping[str, List[str]]
-    extra_names,  # type: Sequence[str]
-):  # type: (...) -> Iterator[str]
+    packages: Iterable[Package],
+    extras: Mapping[NormalizedName, Iterable[NormalizedName]],
+    extra_names: Collection[NormalizedName],
+) -> set[NormalizedName]:
     """
     Returns all package names required by the given extras.
 
@@ -20,40 +25,33 @@ def get_extra_package_names(
         in the `extras` section of `poetry.lock`.
     :param extra_names: A list of strings specifying names of extra groups to resolve.
     """
+    from packaging.utils import canonicalize_name
+
     if not extra_names:
-        return []
+        return set()
 
     # lookup for packages by name, faster than looping over packages repeatedly
     packages_by_name = {package.name: package for package in packages}
 
-    # get and flatten names of packages we've opted into as extras
-    extra_package_names = [
+    # Depth-first search, with our entry points being the packages directly required by
+    # extras.
+    seen_package_names = set()
+    stack = [
         canonicalize_name(extra_package_name)
         for extra_name in extra_names
         for extra_package_name in extras.get(extra_name, ())
     ]
 
-    # keep record of packages seen during recursion in order to avoid recursion error
-    seen_package_names = set()
+    while stack:
+        package_name = stack.pop()
+
+        # We expect to find all packages, but can just carry on if we don't.
+        package = packages_by_name.get(package_name)
+        if package is None or package.name in seen_package_names:
+            continue
+
+        seen_package_names.add(package.name)
+
+        stack += [dependency.name for dependency in package.requires]
 
-    def _extra_packages(package_names):
-        """Recursively find dependencies for packages names"""
-        # for each extra pacakge name
-        for package_name in package_names:
-            # Find the actual Package object. A missing key indicates an implicit
-            # dependency (like setuptools), which should be ignored
-            package = packages_by_name.get(canonicalize_name(package_name))
-            if package:
-                if package.name not in seen_package_names:
-                    seen_package_names.add(package.name)
-                    yield package.name
-                # Recurse for dependencies
-                for dependency_package_name in _extra_packages(
-                    dependency.name
-                    for dependency in package.requires
-                    if dependency.name not in seen_package_names
-                ):
-                    seen_package_names.add(dependency_package_name)
-                    yield dependency_package_name
-
-    return _extra_packages(extra_package_names)
+    return seen_package_names
diff --git a/conda_lock/_vendor/poetry/utils/helpers.py b/conda_lock/_vendor/poetry/utils/helpers.py
index cd01c464c..df0275378 100644
--- a/conda_lock/_vendor/poetry/utils/helpers.py
+++ b/conda_lock/_vendor/poetry/utils/helpers.py
@@ -1,73 +1,62 @@
+from __future__ import annotations
+
+import hashlib
+import io
 import os
-import re
 import shutil
 import stat
+import sys
 import tempfile
 
 from contextlib import contextmanager
-from typing import List
-from typing import Optional
-
-import requests
-
-from conda_lock._vendor.poetry.config.config import Config
-from conda_lock._vendor.poetry.core.packages.package import Package
-from conda_lock._vendor.poetry.core.version import Version
-from conda_lock._vendor.poetry.utils._compat import Path
-
-
-try:
-    from collections.abc import Mapping
-except ImportError:
-    from collections import Mapping
-
-
-_canonicalize_regex = re.compile("[-_]+")
-
+from pathlib import Path
+from typing import TYPE_CHECKING
+from typing import Any
+from typing import Iterator
+from typing import Mapping
 
-def canonicalize_name(name):  # type: (str) -> str
-    return _canonicalize_regex.sub("-", name).lower()
+from conda_lock._vendor.poetry.utils.constants import REQUESTS_TIMEOUT
 
 
-def module_name(name):  # type: (str) -> str
-    return canonicalize_name(name).replace(".", "_").replace("-", "_")
+if TYPE_CHECKING:
+    from collections.abc import Callable
+    from io import BufferedWriter
 
+    from conda_lock._vendor.poetry.core.packages.package import Package
+    from requests import Session
 
-def normalize_version(version):  # type: (str) -> str
-    return str(Version(version))
-
-
-def _del_ro(action, name, exc):
-    os.chmod(name, stat.S_IWRITE)
-    os.remove(name)
+    from conda_lock._vendor.poetry.utils.authenticator import Authenticator
 
 
 @contextmanager
-def temporary_directory(*args, **kwargs):
-    name = tempfile.mkdtemp(*args, **kwargs)
-
-    yield name
-
-    shutil.rmtree(name, onerror=_del_ro)
-
-
-def get_cert(config, repository_name):  # type: (Config, str) -> Optional[Path]
-    cert = config.get("certificates.{}.cert".format(repository_name))
-    if cert:
-        return Path(cert)
-    else:
-        return None
+def directory(path: Path) -> Iterator[Path]:
+    cwd = Path.cwd()
+    try:
+        os.chdir(path)
+        yield path
+    finally:
+        os.chdir(cwd)
 
 
-def get_client_cert(config, repository_name):  # type: (Config, str) -> Optional[Path]
-    client_cert = config.get("certificates.{}.client-cert".format(repository_name))
-    if client_cert:
-        return Path(client_cert)
-    else:
-        return None
+@contextmanager
+def atomic_open(filename: str | os.PathLike[str]) -> Iterator[BufferedWriter]:
+    """
+    write a file to the disk in an atomic fashion
+
+    Taken from requests.utils
+    (https://github.com/psf/requests/blob/7104ad4b135daab0ed19d8e41bd469874702342b/requests/utils.py#L296)
+    """
+    tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
+    try:
+        with os.fdopen(tmp_descriptor, "wb") as tmp_handler:
+            yield tmp_handler
+        os.replace(tmp_name, filename)
+    except BaseException:
+        os.remove(tmp_name)
+        raise
 
 
-def _on_rm_error(func, path, exc_info):
+def _on_rm_error(func: Callable[[str], None], path: str, exc_info: Exception) -> None:
     if not os.path.exists(path):
         return
 
@@ -75,15 +64,25 @@ def _on_rm_error(func, path, exc_info):
     func(path)
 
 
-def safe_rmtree(path):
+def remove_directory(
+    path: Path | str, *args: Any, force: bool = False, **kwargs: Any
+) -> None:
+    """
+    Helper function handle safe removal, and optionally forces stubborn file removal.
+    This is particularly useful when dist files are read-only or git writes read-only
+    files on Windows.
+
+    Internally, all arguments are passed to `shutil.rmtree`.
+    """
     if Path(path).is_symlink():
         return os.unlink(str(path))
 
-    shutil.rmtree(path, onerror=_on_rm_error)
+    kwargs["onerror"] = kwargs.pop("onerror", _on_rm_error if force else None)
+    shutil.rmtree(path, *args, **kwargs)
 
 
-def merge_dicts(d1, d2):
-    for k, v in d2.items():
+def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None:
+    for k in d2.keys():
         if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], Mapping):
             merge_dicts(d1[k], d2[k])
         else:
@@ -91,36 +90,67 @@ def merge_dicts(d1, d2):
 
 
 def download_file(
-    url, dest, session=None, chunk_size=1024
-):  # type: (str, str, Optional[requests.Session], int) -> None
+    url: str,
+    dest: Path,
+    session: Authenticator | Session | None = None,
+    chunk_size: int = 1024,
+) -> None:
+    import requests
+
+    from conda_lock._vendor.poetry.puzzle.provider import Indicator
+
     get = requests.get if not session else session.get
 
-    with get(url, stream=True) as response:
-        response.raise_for_status()
+    response = get(url, stream=True, timeout=REQUESTS_TIMEOUT)
+    response.raise_for_status()
+
+    set_indicator = False
+    with Indicator.context() as update_context:
+        update_context(f"Downloading {url}")
+
+        if "Content-Length" in response.headers:
+            try:
+                total_size = int(response.headers["Content-Length"])
+            except ValueError:
+                total_size = 0
+
+            fetched_size = 0
+            last_percent = 0
+
+            # if less than 1MB, we simply show that we're downloading
+            # but skip the updating
+            set_indicator = total_size > 1024 * 1024
 
         with open(dest, "wb") as f:
             for chunk in response.iter_content(chunk_size=chunk_size):
                 if chunk:
                     f.write(chunk)
 
+                    if set_indicator:
+                        fetched_size += len(chunk)
+                        percent = (fetched_size * 100) // total_size
+                        if percent > last_percent:
+                            last_percent = percent
+                            update_context(f"Downloading {url} {percent:3}%")
+
 
 def get_package_version_display_string(
-    package, root=None
-):  # type: (Package, Optional[Path]) -> str
+    package: Package, root: Path | None = None
+) -> str:
     if package.source_type in ["file", "directory"] and root:
-        return "{} {}".format(
-            package.version,
-            Path(os.path.relpath(package.source_url, root.as_posix())).as_posix(),
-        )
+        assert package.source_url is not None
+        path = Path(os.path.relpath(package.source_url, root)).as_posix()
+        return f"{package.version} {path}"
 
-    return package.full_pretty_version
+    pretty_version: str = package.full_pretty_version
+    return pretty_version
 
 
-def paths_csv(paths):  # type: (List[Path]) -> str
-    return ", ".join('"{}"'.format(str(c)) for c in paths)
+def paths_csv(paths: list[Path]) -> str:
+    return ", ".join(f'"{c!s}"' for c in paths)
 
 
-def is_dir_writable(path, create=False):  # type: (Path, bool) -> bool
+def is_dir_writable(path: Path, create: bool = False) -> bool:
     try:
         if not path.exists():
             if not create:
@@ -129,7 +159,107 @@ def is_dir_writable(path, create=False):  # type: (Path, bool) -> bool
 
         with tempfile.TemporaryFile(dir=str(path)):
             pass
-    except (IOError, OSError):
+    except OSError:
         return False
     else:
         return True
+
+
+def pluralize(count: int, word: str = "") -> str:
+    if count == 1:
+        return word
+    return word + "s"
+
+
+def _get_win_folder_from_registry(csidl_name: str) -> str:
+    if sys.platform != "win32":
+        raise RuntimeError("Method can only be called on Windows.")
+
+    import winreg as _winreg
+
+    shell_folder_name = {
+        "CSIDL_APPDATA": "AppData",
+        "CSIDL_COMMON_APPDATA": "Common AppData",
+        "CSIDL_LOCAL_APPDATA": "Local AppData",
+        "CSIDL_PROGRAM_FILES": "Program Files",
+    }[csidl_name]
+
+    key = _winreg.OpenKey(
+        _winreg.HKEY_CURRENT_USER,
+        r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
+    )
+    dir, type = _winreg.QueryValueEx(key, shell_folder_name)
+
+    assert isinstance(dir, str)
+    return dir
+
+
+def _get_win_folder_with_ctypes(csidl_name: str) -> str:
+    if sys.platform != "win32":
+        raise RuntimeError("Method can only be called on Windows.")
+
+    import ctypes
+
+    csidl_const = {
+        "CSIDL_APPDATA": 26,
+        "CSIDL_COMMON_APPDATA": 35,
+        "CSIDL_LOCAL_APPDATA": 28,
+        "CSIDL_PROGRAM_FILES": 38,
+    }[csidl_name]
+
+    buf = ctypes.create_unicode_buffer(1024)
+    ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
+
+    # Downgrade to short path name if have highbit chars. See
+    # .
+    has_high_char = False
+    for c in buf:
+        if ord(c) > 255:
+            has_high_char = True
+            break
+    if has_high_char:
+        buf2 = ctypes.create_unicode_buffer(1024)
+        if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
+            buf = buf2
+
+    return buf.value
+
+
+def get_win_folder(csidl_name: str) -> Path:
+    if sys.platform == "win32":
+        try:
+            from ctypes import windll  # noqa: F401
+
+            _get_win_folder = _get_win_folder_with_ctypes
+        except ImportError:
+            _get_win_folder = _get_win_folder_from_registry
+
+        return Path(_get_win_folder(csidl_name))
+
+    raise RuntimeError("Method can only be called on Windows.")
+
+
+def get_real_windows_path(path: str | Path) -> Path:
+    program_files = get_win_folder("CSIDL_PROGRAM_FILES")
+    local_appdata = get_win_folder("CSIDL_LOCAL_APPDATA")
+
+    path = Path(
+        str(path).replace(
+            str(program_files / "WindowsApps"),
+            str(local_appdata / "Microsoft/WindowsApps"),
+        )
+    )
+
+    if path.as_posix().startswith(local_appdata.as_posix()):
+        path = path.resolve()
+
+    return path
+
+
+def get_file_hash(path: Path, hash_name: str = "sha256") -> str:
+    h = hashlib.new(hash_name)
+    with path.open("rb") as fp:
+        for content in iter(lambda: fp.read(io.DEFAULT_BUFFER_SIZE), b""):
+            h.update(content)
+
+    return h.hexdigest()
diff --git a/conda_lock/_vendor/poetry/utils/password_manager.py b/conda_lock/_vendor/poetry/utils/password_manager.py
index 24a615a46..fdde6fc98 100644
--- a/conda_lock/_vendor/poetry/utils/password_manager.py
+++ b/conda_lock/_vendor/poetry/utils/password_manager.py
@@ -1,32 +1,64 @@
+from __future__ import annotations
+
+import dataclasses
 import logging
 
+from contextlib import suppress
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.config.config import Config
 
 logger = logging.getLogger(__name__)
 
 
 class PasswordManagerError(Exception):
-
     pass
 
 
-class KeyRingError(Exception):
-
+class PoetryKeyringError(Exception):
     pass
 
 
-class KeyRing:
-    def __init__(self, namespace):
+@dataclasses.dataclass
+class HTTPAuthCredential:
+    username: str | None = dataclasses.field(default=None)
+    password: str | None = dataclasses.field(default=None)
+
+
+class PoetryKeyring:
+    def __init__(self, namespace: str) -> None:
         self._namespace = namespace
         self._is_available = True
 
         self._check()
 
-    def is_available(self):
+    def is_available(self) -> bool:
         return self._is_available
 
-    def get_password(self, name, username):
+    def get_credential(
+        self, *names: str, username: str | None = None
+    ) -> HTTPAuthCredential:
+        default = HTTPAuthCredential(username=username, password=None)
+
         if not self.is_available():
-            return
+            return default
+
+        import keyring
+
+        for name in names:
+            credential = keyring.get_credential(name, username)
+            if credential:
+                return HTTPAuthCredential(
+                    username=credential.username, password=credential.password
+                )
+
+        return default
+
+    def get_password(self, name: str, username: str) -> str | None:
+        if not self.is_available():
+            return None
 
         import keyring
         import keyring.errors
@@ -36,11 +68,11 @@ def get_password(self, name, username):
         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)
+            raise PoetryKeyringError(
+                f"Unable to retrieve the password for {name} from the key ring"
             )
 
-    def set_password(self, name, username, password):
+    def set_password(self, name: str, username: str, password: str) -> None:
         if not self.is_available():
             return
 
@@ -52,17 +84,14 @@ def set_password(self, name, username, password):
         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)
-                )
+            raise PoetryKeyringError(
+                f"Unable to store the password for {name} in the key ring: {e}"
             )
 
-    def delete_password(self, name, username):
+    def delete_password(self, name: str, username: str) -> None:
         if not self.is_available():
             return
 
-        import keyring
         import keyring.errors
 
         name = self.get_entry_name(name)
@@ -70,25 +99,25 @@ def delete_password(self, name, username):
         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)
+            raise PoetryKeyringError(
+                f"Unable to delete the password for {name} from the key ring"
             )
 
-    def get_entry_name(self, name):
-        return "{}-{}".format(self._namespace, name)
+    def get_entry_name(self, name: str) -> str:
+        return f"{self._namespace}-{name}"
 
-    def _check(self):
+    def _check(self) -> None:
         try:
             import keyring
-        except Exception as e:
-            logger.debug("An error occurred while importing keyring: {}".format(str(e)))
+        except ImportError as e:
+            logger.debug("An error occurred while importing keyring: %s", e)
             self._is_available = False
 
             return
 
         backend = keyring.get_keyring()
         name = backend.name.split(" ")[0]
-        if name == "fail":
+        if name in ("fail", "null"):
             logger.debug("No suitable keyring backend found")
             self._is_available = False
         elif "plaintext" in backend.name.lower():
@@ -101,62 +130,73 @@ def _check(self):
                 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
-                    ]
+                    b.name.split(" ")[0] not in ["chainer", "fail", "null"]
+                    and "plaintext" not in b.name.lower()
+                    for b in backends
                 )
-            except Exception:
+            except ImportError:
                 self._is_available = False
 
         if not self._is_available:
-            logger.warning("No suitable keyring backends were found")
+            logger.debug("No suitable keyring backends were found")
 
 
 class PasswordManager:
-    def __init__(self, config):
+    def __init__(self, config: Config) -> None:
         self._config = config
-        self._keyring = None
+        self._keyring: PoetryKeyring | None = None
 
     @property
-    def keyring(self):
+    def keyring(self) -> PoetryKeyring:
         if self._keyring is None:
-            self._keyring = KeyRing("poetry-repository")
+            self._keyring = PoetryKeyring("poetry-repository")
+
             if not self._keyring.is_available():
-                logger.warning(
-                    "Using a plaintext file to store and retrieve credentials"
+                logger.debug(
+                    "Keyring is not available, credentials will be stored and "
+                    "retrieved from configuration files as plaintext."
                 )
 
         return self._keyring
 
-    def set_pypi_token(self, name, token):
+    @staticmethod
+    def warn_plaintext_credentials_stored() -> None:
+        logger.warning("Using a plaintext file to store credentials")
+
+    def set_pypi_token(self, name: str, token: str) -> None:
         if not self.keyring.is_available():
-            self._config.auth_config_source.add_property(
-                "pypi-token.{}".format(name), token
-            )
+            self.warn_plaintext_credentials_stored()
+            self._config.auth_config_source.add_property(f"pypi-token.{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))
+    def get_pypi_token(self, repo_name: str) -> str | None:
+        """Get PyPi token.
 
-        return self.keyring.get_password(name, "__token__")
+        First checks the environment variables for a token,
+        then the configured username/password and the
+        available keyring.
 
-    def delete_pypi_token(self, name):
+        :param repo_name:  Name of repository.
+        :return: Returns a token as a string if found, otherwise None.
+        """
+        token: str | None = self._config.get(f"pypi-token.{repo_name}")
+        if token:
+            return token
+
+        return self.keyring.get_password(repo_name, "__token__")
+
+    def delete_pypi_token(self, name: str) -> None:
         if not self.keyring.is_available():
-            return self._config.auth_config_source.remove_property(
-                "pypi-token.{}".format(name)
-            )
+            return self._config.auth_config_source.remove_property(f"pypi-token.{name}")
 
         self.keyring.delete_password(name, "__token__")
 
-    def get_http_auth(self, name):
-        auth = self._config.get("http-basic.{}".format(name))
+    def get_http_auth(self, name: str) -> dict[str, str | None] | None:
+        auth = self._config.get(f"http-basic.{name}")
         if not auth:
-            username = self._config.get("http-basic.{}.username".format(name))
-            password = self._config.get("http-basic.{}.password".format(name))
+            username = self._config.get(f"http-basic.{name}.username")
+            password = self._config.get(f"http-basic.{name}.password")
             if not username and not password:
                 return None
         else:
@@ -169,24 +209,27 @@ def get_http_auth(self, name):
             "password": password,
         }
 
-    def set_http_password(self, name, username, password):
+    def set_http_password(self, name: str, username: str, password: str) -> None:
         auth = {"username": username}
 
         if not self.keyring.is_available():
+            self.warn_plaintext_credentials_stored()
             auth["password"] = password
         else:
             self.keyring.set_password(name, username, password)
 
-        self._config.auth_config_source.add_property("http-basic.{}".format(name), auth)
+        self._config.auth_config_source.add_property(f"http-basic.{name}", auth)
 
-    def delete_http_password(self, name):
+    def delete_http_password(self, name: str) -> None:
         auth = self.get_http_auth(name)
-        if not auth or "username" not in auth:
+        if not auth:
             return
 
-        try:
-            self.keyring.delete_password(name, auth["username"])
-        except KeyRingError:
-            pass
+        username = auth.get("username")
+        if username is None:
+            return
+
+        with suppress(PoetryKeyringError):
+            self.keyring.delete_password(name, username)
 
-        self._config.auth_config_source.remove_property("http-basic.{}".format(name))
+        self._config.auth_config_source.remove_property(f"http-basic.{name}")
diff --git a/conda_lock/_vendor/poetry/utils/patterns.py b/conda_lock/_vendor/poetry/utils/patterns.py
index ec6c53d78..bf88e51b9 100644
--- a/conda_lock/_vendor/poetry/utils/patterns.py
+++ b/conda_lock/_vendor/poetry/utils/patterns.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import re
 
 
@@ -10,3 +12,8 @@
     r"\.whl|\.dist-info$",
     re.VERBOSE,
 )
+
+sdist_file_re = re.compile(
+    r"^(?P(?P.+?)-(?P\d.*?))"
+    r"(\.sdist)?\.(?P(zip|tar(\.(gz|bz2|xz|Z))?))$"
+)
diff --git a/conda_lock/_vendor/poetry/utils/pip.py b/conda_lock/_vendor/poetry/utils/pip.py
new file mode 100644
index 000000000..a5d91dc9a
--- /dev/null
+++ b/conda_lock/_vendor/poetry/utils/pip.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from conda_lock._vendor.poetry.exceptions import PoetryException
+from conda_lock._vendor.poetry.utils.env import EnvCommandError
+
+
+if TYPE_CHECKING:
+    from pathlib import Path
+
+    from conda_lock._vendor.poetry.utils.env import Env
+
+
+def pip_install(
+    path: Path,
+    environment: Env,
+    editable: bool = False,
+    deps: bool = False,
+    upgrade: bool = False,
+) -> int | str:
+    is_wheel = path.suffix == ".whl"
+
+    # We disable version check here as we are already pinning to version available in
+    # either the virtual environment or the virtualenv package embedded wheel. Version
+    # checks are a wasteful network call that adds a lot of wait time when installing a
+    # lot of packages.
+    args = [
+        "install",
+        "--disable-pip-version-check",
+        "--isolated",
+        "--no-input",
+        "--prefix",
+        str(environment.path),
+    ]
+
+    if not is_wheel and not editable:
+        args.insert(1, "--use-pep517")
+
+    if upgrade:
+        args.append("--upgrade")
+
+    if not deps:
+        args.append("--no-deps")
+
+    if editable:
+        if not path.is_dir():
+            raise PoetryException(
+                "Cannot install non directory dependencies in editable mode"
+            )
+        args.append("-e")
+
+    args.append(str(path))
+
+    try:
+        return environment.run_pip(*args)
+    except EnvCommandError as e:
+        raise PoetryException(f"Failed to install {path.as_posix()}") from e
diff --git a/conda_lock/_vendor/poetry/utils/setup_reader.py b/conda_lock/_vendor/poetry/utils/setup_reader.py
index 8cd2ebd61..aa763b36f 100644
--- a/conda_lock/_vendor/poetry/utils/setup_reader.py
+++ b/conda_lock/_vendor/poetry/utils/setup_reader.py
@@ -1,32 +1,20 @@
+from __future__ import annotations
+
 import ast
 
+from configparser import ConfigParser
+from pathlib import Path
 from typing import Any
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Union
-
-from conda_lock._vendor.poetry.core.semver import Version
-
-from ._compat import PY35
-from ._compat import Path
-from ._compat import basestring
 
+from conda_lock._vendor.poetry.core.constraints.version import Version
 
-try:
-    from configparser import ConfigParser
-except ImportError:
-    from ConfigParser import ConfigParser
 
-
-class SetupReader(object):
+class SetupReader:
     """
     Class that reads a setup.py file without executing it.
     """
 
-    DEFAULT = {
+    DEFAULT: dict[str, Any] = {
         "name": None,
         "version": None,
         "install_requires": [],
@@ -37,10 +25,8 @@ class SetupReader(object):
     FILES = ["setup.py", "setup.cfg"]
 
     @classmethod
-    def read_from_directory(
-        cls, directory
-    ):  # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]]
-        if isinstance(directory, basestring):
+    def read_from_directory(cls, directory: str | Path) -> dict[str, Any]:
+        if isinstance(directory, str):
             directory = Path(directory)
 
         result = cls.DEFAULT.copy()
@@ -49,9 +35,8 @@ def read_from_directory(
             if not filepath.exists():
                 continue
 
-            new_result = getattr(cls(), "read_{}".format(filename.replace(".", "_")))(
-                filepath
-            )
+            read_file_func = getattr(cls(), "read_" + filename.replace(".", "_"))
+            new_result = read_file_func(filepath)
 
             for key in result.keys():
                 if new_result[key]:
@@ -59,48 +44,34 @@ def read_from_directory(
 
         return result
 
-    @classmethod
-    def _is_empty_result(cls, result):  # type: (Dict[str, Any]) -> bool
-        return (
-            not result["install_requires"]
-            and not result["extras_require"]
-            and not result["python_requires"]
-        )
-
-    def read_setup_py(
-        self, filepath
-    ):  # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]]
-        if not PY35:
-            return self.DEFAULT
-
-        if isinstance(filepath, basestring):
+    def read_setup_py(self, filepath: str | Path) -> dict[str, Any]:
+        if isinstance(filepath, str):
             filepath = Path(filepath)
 
         with filepath.open(encoding="utf-8") as f:
             content = f.read()
 
-        result = {}
+        result: dict[str, Any] = {}
 
         body = ast.parse(content).body
 
-        setup_call, body = self._find_setup_call(body)
-        if not setup_call:
+        setup_call = self._find_setup_call(body)
+        if setup_call is None:
             return self.DEFAULT
 
         # Inspecting keyword arguments
-        result["name"] = self._find_single_string(setup_call, body, "name")
-        result["version"] = self._find_single_string(setup_call, body, "version")
-        result["install_requires"] = self._find_install_requires(setup_call, body)
-        result["extras_require"] = self._find_extras_require(setup_call, body)
+        call, body = setup_call
+        result["name"] = self._find_single_string(call, body, "name")
+        result["version"] = self._find_single_string(call, body, "version")
+        result["install_requires"] = self._find_install_requires(call, body)
+        result["extras_require"] = self._find_extras_require(call, body)
         result["python_requires"] = self._find_single_string(
-            setup_call, body, "python_requires"
+            call, body, "python_requires"
         )
 
         return result
 
-    def read_setup_cfg(
-        self, filepath
-    ):  # type: (Union[basestring, Path]) -> Dict[str, Union[List, Dict]]
+    def read_setup_cfg(self, filepath: str | Path) -> dict[str, Any]:
         parser = ConfigParser()
 
         parser.read(str(filepath))
@@ -114,7 +85,7 @@ def read_setup_cfg(
             version = Version.parse(parser.get("metadata", "version")).text
 
         install_requires = []
-        extras_require = {}
+        extras_require: dict[str, list[str]] = {}
         python_requires = None
         if parser.has_section("options"):
             if parser.has_option("options", "install_requires"):
@@ -148,9 +119,9 @@ def read_setup_cfg(
         }
 
     def _find_setup_call(
-        self, elements
-    ):  # type: (List[Any]) -> Tuple[Optional[ast.Call], Optional[List[Any]]]
-        funcdefs = []
+        self, elements: list[ast.stmt]
+    ) -> tuple[ast.Call, list[ast.stmt]] | None:
+        funcdefs: list[ast.stmt] = []
         for i, element in enumerate(elements):
             if isinstance(element, ast.If) and i == len(elements) - 1:
                 # Checking if the last element is an if statement
@@ -167,11 +138,13 @@ def _find_setup_call(
                 if left.id != "__name__":
                     continue
 
-                setup_call, body = self._find_sub_setup_call([element])
-                if not setup_call:
+                setup_call = self._find_sub_setup_call([element])
+                if setup_call is None:
                     continue
 
-                return setup_call, body + elements
+                call, body = setup_call
+                return call, body + elements
+
             if not isinstance(element, ast.Expr):
                 if isinstance(element, ast.FunctionDef):
                     funcdefs.append(element)
@@ -185,8 +158,7 @@ def _find_setup_call(
             func = value.func
             if not (isinstance(func, ast.Name) and func.id == "setup") and not (
                 isinstance(func, ast.Attribute)
-                and hasattr(func.value, "id")
-                and func.value.id == "setuptools"
+                and getattr(func.value, "id", None) == "setuptools"
                 and func.attr == "setup"
             ):
                 continue
@@ -197,26 +169,24 @@ def _find_setup_call(
         return self._find_sub_setup_call(funcdefs)
 
     def _find_sub_setup_call(
-        self, elements
-    ):  # type: (List[Any]) -> Tuple[Optional[ast.Call], Optional[List[Any]]]
+        self, elements: list[ast.stmt]
+    ) -> tuple[ast.Call, list[ast.stmt]] | None:
         for element in elements:
             if not isinstance(element, (ast.FunctionDef, ast.If)):
                 continue
 
             setup_call = self._find_setup_call(element.body)
-            if setup_call != (None, None):
-                setup_call, body = setup_call
+            if setup_call is not None:
+                sub_call, body = setup_call
 
                 body = elements + body
 
-                return setup_call, body
+                return sub_call, body
 
-        return None, None
+        return None
 
-    def _find_install_requires(
-        self, call, body
-    ):  # type: (ast.Call, Iterable[Any]) -> List[str]
-        install_requires = []
+    def _find_install_requires(self, call: ast.Call, body: list[ast.stmt]) -> list[str]:
+        install_requires: list[str] = []
         value = self._find_in_call(call, "install_requires")
         if value is None:
             # Trying to find in kwargs
@@ -245,20 +215,22 @@ def _find_install_requires(
 
         if isinstance(value, ast.List):
             for el in value.elts:
-                install_requires.append(el.s)
+                if isinstance(el, ast.Str):
+                    install_requires.append(el.s)
         elif isinstance(value, ast.Name):
             variable = self._find_variable_in_body(body, value.id)
 
             if variable is not None and isinstance(variable, ast.List):
                 for el in variable.elts:
-                    install_requires.append(el.s)
+                    if isinstance(el, ast.Str):
+                        install_requires.append(el.s)
 
         return install_requires
 
     def _find_extras_require(
-        self, call, body
-    ):  # type: (ast.Call, Iterable[Any]) -> Dict[str, List]
-        extras_require = {}
+        self, call: ast.Call, body: list[ast.stmt]
+    ) -> dict[str, list[str]]:
+        extras_require: dict[str, list[str]] = {}
         value = self._find_in_call(call, "extras_require")
         if value is None:
             # Trying to find in kwargs
@@ -286,12 +258,18 @@ def _find_extras_require(
             return extras_require
 
         if isinstance(value, ast.Dict):
+            val: ast.expr | None
             for key, val in zip(value.keys, value.values):
+                if not isinstance(key, ast.Str):
+                    continue
+
                 if isinstance(val, ast.Name):
                     val = self._find_variable_in_body(body, val.id)
 
                 if isinstance(val, ast.List):
-                    extras_require[key.s] = [e.s for e in val.elts]
+                    extras_require[key.s] = [
+                        e.s for e in val.elts if isinstance(e, ast.Str)
+                    ]
         elif isinstance(value, ast.Name):
             variable = self._find_variable_in_body(body, value.id)
 
@@ -299,42 +277,47 @@ def _find_extras_require(
                 return extras_require
 
             for key, val in zip(variable.keys, variable.values):
+                if not isinstance(key, ast.Str):
+                    continue
+
                 if isinstance(val, ast.Name):
                     val = self._find_variable_in_body(body, val.id)
 
                 if isinstance(val, ast.List):
-                    extras_require[key.s] = [e.s for e in val.elts]
+                    extras_require[key.s] = [
+                        e.s for e in val.elts if isinstance(e, ast.Str)
+                    ]
 
         return extras_require
 
     def _find_single_string(
-        self, call, body, name
-    ):  # type: (ast.Call, List[Any], str) -> Optional[str]
+        self, call: ast.Call, body: list[ast.stmt], name: str
+    ) -> str | None:
         value = self._find_in_call(call, name)
         if value is None:
             # Trying to find in kwargs
             kwargs = self._find_call_kwargs(call)
 
             if kwargs is None or not isinstance(kwargs, ast.Name):
-                return
+                return None
 
             variable = self._find_variable_in_body(body, kwargs.id)
             if not isinstance(variable, (ast.Dict, ast.Call)):
-                return
+                return None
 
             if isinstance(variable, ast.Call):
                 if not isinstance(variable.func, ast.Name):
-                    return
+                    return None
 
                 if variable.func.id != "dict":
-                    return
+                    return None
 
                 value = self._find_in_call(variable, name)
             else:
                 value = self._find_in_dict(variable, name)
 
         if value is None:
-            return
+            return None
 
         if isinstance(value, ast.Str):
             return value.s
@@ -344,12 +327,15 @@ def _find_single_string(
             if variable is not None and isinstance(variable, ast.Str):
                 return variable.s
 
-    def _find_in_call(self, call, name):  # type: (ast.Call, str) -> Optional[Any]
+        return None
+
+    def _find_in_call(self, call: ast.Call, name: str) -> Any | None:
         for keyword in call.keywords:
             if keyword.arg == name:
                 return keyword.value
+        return None
 
-    def _find_call_kwargs(self, call):  # type: (ast.Call) -> Optional[Any]
+    def _find_call_kwargs(self, call: ast.Call) -> Any | None:
         kwargs = None
         for keyword in call.keywords:
             if keyword.arg is None:
@@ -358,13 +344,9 @@ def _find_call_kwargs(self, call):  # type: (ast.Call) -> Optional[Any]
         return kwargs
 
     def _find_variable_in_body(
-        self, body, name
-    ):  # type: (Iterable[Any], str) -> Optional[Any]
-        found = None
+        self, body: list[ast.stmt], name: str
+    ) -> ast.expr | None:
         for elem in body:
-            if found:
-                break
-
             if not isinstance(elem, ast.Assign):
                 continue
 
@@ -375,7 +357,11 @@ def _find_variable_in_body(
                 if target.id == name:
                     return elem.value
 
-    def _find_in_dict(self, dict_, name):  # type: (ast.Call, str) -> Optional[Any]
+        return None
+
+    def _find_in_dict(self, dict_: ast.Dict, name: str) -> ast.expr | None:
         for key, val in zip(dict_.keys, dict_.values):
             if isinstance(key, ast.Str) and key.s == name:
                 return val
+
+        return None
diff --git a/conda_lock/_vendor/poetry/utils/source.py b/conda_lock/_vendor/poetry/utils/source.py
new file mode 100644
index 000000000..3fa6fb3dc
--- /dev/null
+++ b/conda_lock/_vendor/poetry/utils/source.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from tomlkit.items import Table
+
+    from conda_lock._vendor.poetry.config.source import Source
+
+
+def source_to_table(source: Source) -> Table:
+    from tomlkit import nl
+    from tomlkit import table
+
+    source_table: Table = table()
+    for key, value in source.to_dict().items():
+        source_table.add(key, value)
+    source_table.add(nl())
+    return source_table
diff --git a/conda_lock/_vendor/poetry/vcs/__init__.py b/conda_lock/_vendor/poetry/vcs/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conda_lock/_vendor/poetry/vcs/git/__init__.py b/conda_lock/_vendor/poetry/vcs/git/__init__.py
new file mode 100644
index 000000000..2dc28b9ee
--- /dev/null
+++ b/conda_lock/_vendor/poetry/vcs/git/__init__.py
@@ -0,0 +1,6 @@
+from __future__ import annotations
+
+from conda_lock._vendor.poetry.vcs.git.backend import Git
+
+
+__all__ = ["Git"]
diff --git a/conda_lock/_vendor/poetry/vcs/git/backend.py b/conda_lock/_vendor/poetry/vcs/git/backend.py
new file mode 100644
index 000000000..d4a4f3afc
--- /dev/null
+++ b/conda_lock/_vendor/poetry/vcs/git/backend.py
@@ -0,0 +1,442 @@
+from __future__ import annotations
+
+import dataclasses
+import logging
+import re
+
+from pathlib import Path
+from subprocess import CalledProcessError
+from typing import TYPE_CHECKING
+
+from dulwich import porcelain
+from dulwich.client import HTTPUnauthorized
+from dulwich.client import get_transport_and_path
+from dulwich.config import ConfigFile
+from dulwich.config import parse_submodules
+from dulwich.errors import NotGitRepository
+from dulwich.refs import ANNOTATED_TAG_SUFFIX
+from dulwich.repo import Repo
+
+from conda_lock._vendor.poetry.console.exceptions import PoetryConsoleError
+from conda_lock._vendor.poetry.utils.authenticator import get_default_authenticator
+from conda_lock._vendor.poetry.utils.helpers import remove_directory
+
+
+if TYPE_CHECKING:
+    from dulwich.client import FetchPackResult
+    from dulwich.client import GitClient
+
+
+logger = logging.getLogger(__name__)
+
+
+def is_revision_sha(revision: str | None) -> bool:
+    return re.match(r"^\b[0-9a-f]{5,40}\b$", revision or "") is not None
+
+
+def annotated_tag(ref: str | bytes) -> bytes:
+    if isinstance(ref, str):
+        ref = ref.encode("utf-8")
+    return ref + ANNOTATED_TAG_SUFFIX
+
+
+@dataclasses.dataclass
+class GitRefSpec:
+    branch: str | None = None
+    revision: str | None = None
+    tag: str | None = None
+    ref: bytes = dataclasses.field(default_factory=lambda: b"HEAD")
+
+    def resolve(self, remote_refs: FetchPackResult) -> None:
+        """
+        Resolve the ref using the provided remote refs.
+        """
+        self._normalise(remote_refs=remote_refs)
+        self._set_head(remote_refs=remote_refs)
+
+    def _normalise(self, remote_refs: FetchPackResult) -> None:
+        """
+        Internal helper method to determine if given revision is
+            1. a branch or tag; if so, set corresponding properties.
+            2. a short sha; if so, resolve full sha and set as revision
+        """
+        if self.revision:
+            ref = f"refs/tags/{self.revision}".encode()
+            if ref in remote_refs.refs or annotated_tag(ref) in remote_refs.refs:
+                # this is a tag, incorrectly specified as a revision, tags take priority
+                self.tag = self.revision
+                self.revision = None
+            elif (
+                self.revision.encode("utf-8") in remote_refs.refs
+                or f"refs/heads/{self.revision}".encode() in remote_refs.refs
+            ):
+                # this is most likely a ref spec or a branch incorrectly specified
+                self.branch = self.revision
+                self.revision = None
+        elif (
+            self.branch
+            and f"refs/heads/{self.branch}".encode() not in remote_refs.refs
+            and (
+                f"refs/tags/{self.branch}".encode() in remote_refs.refs
+                or annotated_tag(f"refs/tags/{self.branch}") in remote_refs.refs
+            )
+        ):
+            # this is a tag incorrectly specified as a branch
+            self.tag = self.branch
+            self.branch = None
+
+        if self.revision and self.is_sha_short:
+            # revision is a short sha, resolve to full sha
+            short_sha = self.revision.encode("utf-8")
+            for sha in remote_refs.refs.values():
+                if sha.startswith(short_sha):
+                    self.revision = sha.decode("utf-8")
+                    break
+
+    def _set_head(self, remote_refs: FetchPackResult) -> None:
+        """
+        Internal helper method to populate ref and set it's sha as the remote's head
+        and default ref.
+        """
+        self.ref = remote_refs.symrefs[b"HEAD"]
+
+        if self.revision:
+            head = self.revision.encode("utf-8")
+        else:
+            if self.tag:
+                ref = f"refs/tags/{self.tag}".encode()
+                annotated = annotated_tag(ref)
+                self.ref = annotated if annotated in remote_refs.refs else ref
+            elif self.branch:
+                self.ref = (
+                    self.branch.encode("utf-8")
+                    if self.is_ref
+                    else f"refs/heads/{self.branch}".encode()
+                )
+            head = remote_refs.refs[self.ref]
+
+        remote_refs.refs[self.ref] = remote_refs.refs[b"HEAD"] = head
+
+    @property
+    def key(self) -> str:
+        return self.revision or self.branch or self.tag or self.ref.decode("utf-8")
+
+    @property
+    def is_sha(self) -> bool:
+        return is_revision_sha(revision=self.revision)
+
+    @property
+    def is_ref(self) -> bool:
+        return self.branch is not None and self.branch.startswith("refs/")
+
+    @property
+    def is_sha_short(self) -> bool:
+        return self.revision is not None and self.is_sha and len(self.revision) < 40
+
+
+@dataclasses.dataclass
+class GitRepoLocalInfo:
+    repo: dataclasses.InitVar[Repo | Path | str]
+    origin: str = dataclasses.field(init=False)
+    revision: str = dataclasses.field(init=False)
+
+    def __post_init__(self, repo: Repo | Path | str) -> None:
+        repo = Git.as_repo(repo=repo) if not isinstance(repo, Repo) else repo
+        self.origin = Git.get_remote_url(repo=repo, remote="origin")
+        self.revision = Git.get_revision(repo=repo)
+
+
+class Git:
+    @staticmethod
+    def as_repo(repo: Path | str) -> Repo:
+        return Repo(str(repo))
+
+    @staticmethod
+    def get_remote_url(repo: Repo, remote: str = "origin") -> str:
+        with repo:
+            config = repo.get_config()
+            section = (b"remote", remote.encode("utf-8"))
+
+            url = ""
+            if config.has_section(section):
+                value = config.get(section, b"url")
+                url = value.decode("utf-8")
+
+            return url
+
+    @staticmethod
+    def get_revision(repo: Repo) -> str:
+        with repo:
+            return repo.head().decode("utf-8")
+
+    @classmethod
+    def info(cls, repo: Repo | Path | str) -> GitRepoLocalInfo:
+        return GitRepoLocalInfo(repo=repo)
+
+    @staticmethod
+    def get_name_from_source_url(url: str) -> str:
+        return re.sub(r"(.git)?$", "", url.rsplit("/", 1)[-1])
+
+    @classmethod
+    def _fetch_remote_refs(cls, url: str, local: Repo) -> FetchPackResult:
+        """
+        Helper method to fetch remote refs.
+        """
+        client: GitClient
+        path: str
+
+        kwargs: dict[str, str] = {}
+        credentials = get_default_authenticator().get_credentials_for_git_url(url=url)
+
+        if credentials.password and credentials.username:
+            # we do this conditionally as otherwise, dulwich might complain if these
+            # parameters are passed in for an ssh url
+            kwargs["username"] = credentials.username
+            kwargs["password"] = credentials.password
+
+        config = local.get_config_stack()
+        client, path = get_transport_and_path(url, config=config, **kwargs)
+
+        with local:
+            result: FetchPackResult = client.fetch(
+                path,
+                local,
+                determine_wants=local.object_store.determine_wants_all,
+            )
+            return result
+
+    @staticmethod
+    def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo:
+        """
+        Helper method to facilitate fallback to using system provided git client via
+        subprocess calls.
+        """
+        from conda_lock._vendor.poetry.vcs.git.system import SystemGit
+
+        logger.debug("Cloning '%s' using system git client", url)
+
+        if target.exists():
+            remove_directory(path=target, force=True)
+
+        revision = refspec.tag or refspec.branch or refspec.revision or "HEAD"
+
+        try:
+            SystemGit.clone(url, target)
+        except CalledProcessError:
+            raise PoetryConsoleError(
+                f"Failed to clone {url}, check your git configuration and permissions"
+                " for this repository."
+            )
+
+        if revision:
+            revision.replace("refs/head/", "")
+            revision.replace("refs/tags/", "")
+
+        try:
+            SystemGit.checkout(revision, target)
+        except CalledProcessError:
+            raise PoetryConsoleError(f"Failed to checkout {url} at '{revision}'")
+
+        repo = Repo(str(target))
+        return repo
+
+    @classmethod
+    def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo:
+        """
+        Helper method to clone a remove repository at the given `url` at the specified
+        ref spec.
+        """
+        local: Repo
+        if not target.exists():
+            local = Repo.init(str(target), mkdir=True)
+            porcelain.remote_add(local, "origin", url)
+        else:
+            local = Repo(str(target))
+
+        remote_refs = cls._fetch_remote_refs(url=url, local=local)
+
+        logger.debug(
+            "Cloning %s at '%s' to %s", url, refspec.key, target
+        )
+
+        try:
+            refspec.resolve(remote_refs=remote_refs)
+        except KeyError:  # branch / ref does not exist
+            raise PoetryConsoleError(
+                f"Failed to clone {url} at '{refspec.key}', verify ref exists on"
+                " remote."
+            )
+
+        # ensure local HEAD matches remote
+        local.refs[b"HEAD"] = remote_refs.refs[b"HEAD"]
+
+        if refspec.is_ref:
+            # set ref to current HEAD
+            local.refs[refspec.ref] = local.refs[b"HEAD"]
+
+        for base, prefix in {
+            (b"refs/remotes/origin", b"refs/heads/"),
+            (b"refs/tags", b"refs/tags"),
+        }:
+            local.refs.import_refs(
+                base=base,
+                other={
+                    n[len(prefix) :]: v
+                    for (n, v) in remote_refs.refs.items()
+                    if n.startswith(prefix) and not n.endswith(ANNOTATED_TAG_SUFFIX)
+                },
+            )
+
+        try:
+            with local:
+                local.reset_index()
+        except (AssertionError, KeyError) as e:
+            # this implies the ref we need does not exist or is invalid
+            if isinstance(e, KeyError):
+                # the local copy is at a bad state, lets remove it
+                logger.debug(
+                    "Removing local clone (%s) of repository as it is in a"
+                    " broken state.",
+                    local.path,
+                )
+                remove_directory(local.path, force=True)
+
+            if isinstance(e, AssertionError) and "Invalid object name" not in str(e):
+                raise
+
+            logger.debug(
+                "\nRequested ref (%s) was not fetched to local copy and cannot"
+                " be used. The following error was raised:\n\n\t%s",
+                refspec.key,
+                e,
+            )
+
+            raise PoetryConsoleError(
+                f"Failed to clone {url} at '{refspec.key}', verify ref exists on"
+                " remote."
+            )
+
+        return local
+
+    @classmethod
+    def _clone_submodules(cls, repo: Repo) -> None:
+        """
+        Helper method to identify configured submodules and clone them recursively.
+        """
+        repo_root = Path(repo.path)
+        modules_config = repo_root.joinpath(".gitmodules")
+
+        if modules_config.exists():
+            config = ConfigFile.from_path(str(modules_config))
+
+            url: bytes
+            path: bytes
+            submodules = parse_submodules(config)
+            for path, url, name in submodules:
+                path_relative = Path(path.decode("utf-8"))
+                path_absolute = repo_root.joinpath(path_relative)
+
+                source_root = path_absolute.parent
+                source_root.mkdir(parents=True, exist_ok=True)
+
+                with repo:
+                    try:
+                        revision = repo.open_index()[path].sha.decode("utf-8")
+                    except KeyError:
+                        logger.debug(
+                            "Skip submodule %s in %s, path %s not found",
+                            name,
+                            repo.path,
+                            path,
+                        )
+                        continue
+
+                cls.clone(
+                    url=url.decode("utf-8"),
+                    source_root=source_root,
+                    name=path_relative.name,
+                    revision=revision,
+                    clean=path_absolute.exists()
+                    and not path_absolute.joinpath(".git").is_dir(),
+                )
+
+    @staticmethod
+    def is_using_legacy_client() -> bool:
+        from conda_lock._vendor.poetry.config.config import Config
+
+        legacy_client: bool = Config.create().get(
+            "experimental.system-git-client", False
+        )
+        return legacy_client
+
+    @staticmethod
+    def get_default_source_root() -> Path:
+        from conda_lock._vendor.poetry.config.config import Config
+
+        return Path(Config.create().get("cache-dir")) / "src"
+
+    @classmethod
+    def clone(
+        cls,
+        url: str,
+        name: str | None = None,
+        branch: str | None = None,
+        tag: str | None = None,
+        revision: str | None = None,
+        source_root: Path | None = None,
+        clean: bool = False,
+    ) -> Repo:
+        source_root = source_root or cls.get_default_source_root()
+        source_root.mkdir(parents=True, exist_ok=True)
+
+        name = name or cls.get_name_from_source_url(url=url)
+        target = source_root / name
+        refspec = GitRefSpec(branch=branch, revision=revision, tag=tag)
+
+        if target.exists():
+            if clean:
+                # force clean the local copy if it exists, do not reuse
+                remove_directory(target, force=True)
+            else:
+                # check if the current local copy matches the requested ref spec
+                try:
+                    current_repo = Repo(str(target))
+
+                    with current_repo:
+                        current_sha = current_repo.head().decode("utf-8")
+                except (NotGitRepository, AssertionError, KeyError):
+                    # something is wrong with the current checkout, clean it
+                    remove_directory(target, force=True)
+                else:
+                    if not is_revision_sha(revision=current_sha):
+                        # head is not a sha, this will cause issues later, lets reset
+                        remove_directory(target, force=True)
+                    elif (
+                        refspec.is_sha
+                        and refspec.revision is not None
+                        and current_sha.startswith(refspec.revision)
+                    ):
+                        # if revision is used short-circuit remote fetch head matches
+                        return current_repo
+
+        try:
+            if not cls.is_using_legacy_client():
+                local = cls._clone(url=url, refspec=refspec, target=target)
+                cls._clone_submodules(repo=local)
+                return local
+        except HTTPUnauthorized:
+            # we do this here to handle http authenticated repositories as dulwich
+            # does not currently support using credentials from git-credential helpers.
+            # upstream issue: https://github.com/jelmer/dulwich/issues/873
+            #
+            # this is a little inefficient, however preferred as this is transparent
+            # without additional configuration or changes for existing projects that
+            # use http basic auth credentials.
+            logger.debug(
+                "Unable to fetch from private repository '%s', falling back to"
+                " system git",
+                url,
+            )
+
+        # fallback to legacy git client
+        return cls._clone_legacy(url=url, refspec=refspec, target=target)
diff --git a/conda_lock/_vendor/poetry/vcs/git/system.py b/conda_lock/_vendor/poetry/vcs/git/system.py
new file mode 100644
index 000000000..5ed847333
--- /dev/null
+++ b/conda_lock/_vendor/poetry/vcs/git/system.py
@@ -0,0 +1,71 @@
+from __future__ import annotations
+
+import os
+import subprocess
+
+from typing import TYPE_CHECKING
+
+from dulwich.client import find_git_command
+
+
+if TYPE_CHECKING:
+    from pathlib import Path
+    from typing import Any
+
+
+class SystemGit:
+    @classmethod
+    def clone(cls, repository: str, dest: Path) -> str:
+        cls._check_parameter(repository)
+
+        return cls.run("clone", "--recurse-submodules", "--", repository, str(dest))
+
+    @classmethod
+    def checkout(cls, rev: str, target: Path | None = None) -> str:
+        args = []
+
+        if target:
+            args += [
+                "--git-dir",
+                (target / ".git").as_posix(),
+                "--work-tree",
+                target.as_posix(),
+            ]
+
+        cls._check_parameter(rev)
+
+        args += ["checkout", rev]
+
+        return cls.run(*args)
+
+    @staticmethod
+    def run(*args: Any, **kwargs: Any) -> str:
+        folder = kwargs.pop("folder", None)
+        if folder:
+            args = (
+                "--git-dir",
+                (folder / ".git").as_posix(),
+                "--work-tree",
+                folder.as_posix(),
+            ) + args
+
+        git_command = find_git_command()
+        env = os.environ.copy()
+        env["GIT_TERMINAL_PROMPT"] = "0"
+        return (
+            subprocess.check_output(
+                git_command + list(args),
+                stderr=subprocess.STDOUT,
+                env=env,
+            )
+            .decode()
+            .strip()
+        )
+
+    @staticmethod
+    def _check_parameter(parameter: str) -> None:
+        """
+        Checks a git parameter to avoid unwanted code execution.
+        """
+        if parameter.strip().startswith("-"):
+            raise RuntimeError(f"Invalid Git parameter: {parameter}")
diff --git a/conda_lock/_vendor/poetry/version/version_selector.py b/conda_lock/_vendor/poetry/version/version_selector.py
index 08c9013cc..dff1585df 100644
--- a/conda_lock/_vendor/poetry/version/version_selector.py
+++ b/conda_lock/_vendor/poetry/version/version_selector.py
@@ -1,20 +1,27 @@
-from typing import Union
+from __future__ import annotations
 
-from conda_lock._vendor.poetry.core.packages import Package
-from conda_lock._vendor.poetry.core.semver import Version
+from typing import TYPE_CHECKING
 
+from conda_lock._vendor.poetry.core.constraints.version import Version
 
-class VersionSelector(object):
-    def __init__(self, pool):
+
+if TYPE_CHECKING:
+    from conda_lock._vendor.poetry.core.packages.package import Package
+
+    from conda_lock._vendor.poetry.repositories import RepositoryPool
+
+
+class VersionSelector:
+    def __init__(self, pool: RepositoryPool) -> None:
         self._pool = pool
 
     def find_best_candidate(
         self,
-        package_name,  # type: str
-        target_package_version=None,  # type:  Union[str, None]
-        allow_prereleases=False,  # type: bool
-        source=None,  # type: str
-    ):  # type: (...) -> Union[Package, bool]
+        package_name: str,
+        target_package_version: str | None = None,
+        allow_prereleases: bool = False,
+        source: str | None = None,
+    ) -> Package | None:
         """
         Given a package name and optional version,
         returns the latest Package that matches
@@ -25,15 +32,15 @@ def find_best_candidate(
             package_name,
             {
                 "version": target_package_version or "*",
-                "allow_prereleases": allow_prereleases,
+                "allow-prereleases": allow_prereleases,
                 "source": source,
             },
         )
         candidates = self._pool.find_packages(dependency)
-        only_prereleases = all([c.version.is_prerelease() for c in candidates])
+        only_prereleases = all(c.version.is_unstable() for c in candidates)
 
         if not candidates:
-            return False
+            return None
 
         package = None
         for candidate in candidates:
@@ -48,30 +55,15 @@ def find_best_candidate(
             if package is None or package.version < candidate.version:
                 package = candidate
 
-        if package is None:
-            return False
         return package
 
-    def find_recommended_require_version(self, package):
+    def find_recommended_require_version(self, package: Package) -> str:
         version = package.version
 
         return self._transform_version(version.text, package.pretty_version)
 
-    def _transform_version(self, version, pretty_version):
+    def _transform_version(self, version: str, pretty_version: str) -> str:
         try:
-            parsed = Version.parse(version)
-            parts = [parsed.major, parsed.minor, parsed.patch]
+            return f"^{Version.parse(version).to_string()}"
         except ValueError:
             return pretty_version
-
-        parts = parts[: parsed.precision]
-
-        # check to see if we have a semver-looking version
-        if len(parts) < 3:
-            version = pretty_version
-        else:
-            version = ".".join(str(p) for p in parts)
-            if parsed.is_prerelease():
-                version += "-{}".format(".".join(str(p) for p in parsed.prerelease))
-
-        return "^{}".format(version)
diff --git a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/LICENSE b/conda_lock/_vendor/poetry_core.COPYING
similarity index 96%
rename from conda_lock/_vendor/poetry/core/_vendor/jsonschema/LICENSE
rename to conda_lock/_vendor/poetry_core.COPYING
index c28adbadd..af9cfbdb1 100644
--- a/conda_lock/_vendor/poetry/core/_vendor/jsonschema/LICENSE
+++ b/conda_lock/_vendor/poetry_core.COPYING
@@ -1,4 +1,4 @@
-Copyright (c) 2012 Julian Berman
+Copyright (c) 2013 Julian Berman
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/conda_lock/_vendor/poetry-core.LICENSE b/conda_lock/_vendor/poetry_core.LICENSE
similarity index 100%
rename from conda_lock/_vendor/poetry-core.LICENSE
rename to conda_lock/_vendor/poetry_core.LICENSE
diff --git a/conda_lock/_vendor/poetry_core.LICENSE.APACHE b/conda_lock/_vendor/poetry_core.LICENSE.APACHE
new file mode 100644
index 000000000..f433b1a53
--- /dev/null
+++ b/conda_lock/_vendor/poetry_core.LICENSE.APACHE
@@ -0,0 +1,177 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
diff --git a/conda_lock/_vendor/poetry_core.LICENSE.BSD b/conda_lock/_vendor/poetry_core.LICENSE.BSD
new file mode 100644
index 000000000..42ce7b75c
--- /dev/null
+++ b/conda_lock/_vendor/poetry_core.LICENSE.BSD
@@ -0,0 +1,23 @@
+Copyright (c) Donald Stufft and individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/conda_lock/_vendor/poetry_core.LICENSE.mit b/conda_lock/_vendor/poetry_core.LICENSE.mit
new file mode 100644
index 000000000..6cbf251f6
--- /dev/null
+++ b/conda_lock/_vendor/poetry_core.LICENSE.mit
@@ -0,0 +1,22 @@
+Copyright (c) 2022 Tobias Gustafsson
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/conda_lock/_vendor/poetry_core.typing_extensions.LICENSE b/conda_lock/_vendor/poetry_core.typing_extensions.LICENSE
new file mode 100644
index 000000000..1df6b3b8d
--- /dev/null
+++ b/conda_lock/_vendor/poetry_core.typing_extensions.LICENSE
@@ -0,0 +1,254 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC.  Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation.  In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition).  Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+    Release         Derived     Year        Owner       GPL-
+                    from                                compatible? (1)
+
+    0.9.0 thru 1.2              1991-1995   CWI         yes
+    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
+    1.6             1.5.2       2000        CNRI        no
+    2.0             1.6         2000        BeOpen.com  no
+    1.6.1           1.6         2001        CNRI        yes (2)
+    2.1             2.0+1.6.1   2001        PSF         no
+    2.0.1           2.0+1.6.1   2001        PSF         yes
+    2.1.1           2.1+2.0.1   2001        PSF         yes
+    2.1.2           2.1.1       2002        PSF         yes
+    2.1.3           2.1.2       2002        PSF         yes
+    2.2 and above   2.1.1       2001-now    PSF         yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+    the GPL.  All Python licenses, unlike the GPL, let you distribute
+    a modified version without making your changes open source.  The
+    GPL-compatible licenses make it possible to combine Python with
+    other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+    because its license has a choice of law clause.  According to
+    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+    is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee.  This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions.  Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee.  This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party.  As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee.  Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement.  This Agreement together with
+Python 1.6.1 may be located on the internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013.  This
+Agreement may also be obtained from a proxy server on the internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee.  This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+        ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands.  All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/conda_lock/_vendor/vendor.txt b/conda_lock/_vendor/vendor.txt
index 664a8c7a7..93b69f505 100644
--- a/conda_lock/_vendor/vendor.txt
+++ b/conda_lock/_vendor/vendor.txt
@@ -1,7 +1,7 @@
 # Poetry-related:
-cleo==0.8.1
-poetry==1.1.15
-poetry-core==1.0.8
+cleo==2.0.0
+poetry==1.3.2
+poetry-core==1.4.0
 
 # install conda from github
 git+https://github.com/conda/conda.git@22.9.0
diff --git a/conda_lock/pypi_solver.py b/conda_lock/pypi_solver.py
index ca0cd27a3..e5f9ac8d0 100644
--- a/conda_lock/pypi_solver.py
+++ b/conda_lock/pypi_solver.py
@@ -11,12 +11,17 @@
 from packaging.tags import compatible_tags, cpython_tags
 
 from conda_lock import src_parser
-from conda_lock._vendor.poetry.core.packages import Dependency as PoetryDependency
-from conda_lock._vendor.poetry.core.packages import Package as PoetryPackage
-from conda_lock._vendor.poetry.core.packages import (
+from conda_lock._vendor.poetry.config.config import Config as PoetryConfig
+from conda_lock._vendor.poetry.core.packages.dependency import (
+    Dependency as PoetryDependency,
+)
+from conda_lock._vendor.poetry.core.packages.package import Package as PoetryPackage
+from conda_lock._vendor.poetry.core.packages.project_package import (
     ProjectPackage as PoetryProjectPackage,
 )
-from conda_lock._vendor.poetry.core.packages import URLDependency as PoetryURLDependency
+from conda_lock._vendor.poetry.core.packages.url_dependency import (
+    URLDependency as PoetryURLDependency,
+)
 from conda_lock._vendor.poetry.factory import Factory
 from conda_lock._vendor.poetry.installation.chooser import Chooser
 from conda_lock._vendor.poetry.installation.operations.uninstall import Uninstall
@@ -210,10 +215,10 @@ def solve_pypi(
     for dep in dependencies:
         dummy_package.add_dependency(dep)
 
-    factory = Factory()
-    config = factory.create_config()
+    factory = Factory
+    config = PoetryConfig.create()
     repos = [
-        factory.create_legacy_repository(
+        factory.create_package_source(
             {"name": source[0], "url": source[1]["url"]}, config
         )
         for source in config.get("repositories", {}).items()
@@ -222,8 +227,8 @@ def solve_pypi(
     pypi = PyPiRepository()
     pool = Pool(repositories=[*repos, pypi])
 
-    installed = Repository()
-    locked = Repository()
+    installed: "List[PoetryPackage]" = list()
+    locked: "List[PoetryPackage]" = list()
 
     python_packages = dict()
     locked_dep: src_parser.LockedDependency
@@ -242,10 +247,10 @@ def solve_pypi(
     # treat conda packages as both locked and installed
     for name, version in python_packages.items():
         for repo in (locked, installed):
-            repo.add_package(PoetryPackage(name=name, version=version))
+            repo.append(PoetryPackage(name=name, version=version))
     # treat pip packages as locked only
     for spec in pip_locked.values():
-        locked.add_package(get_package(spec))
+        locked.append(get_package(spec))
 
     if verbose:
         io = ConsoleIO()
@@ -266,7 +271,8 @@ def solve_pypi(
     env = PlatformEnv(python_version, platform)
     # find platform-specific solution (e.g. dependencies conditioned on markers)
     with s.use_environment(env):
-        result = s.solve(use_latest=to_update)
+        trx = s.solve(use_latest=to_update)
+        result = trx.calculate_operations(with_uninstalls=False)
 
     chooser = Chooser(pool, env=env)
 
diff --git a/conda_lock/scripts/vendor_poetry/migration.py b/conda_lock/scripts/vendor_poetry/migration.py
index 69cc8c291..57fedab51 100644
--- a/conda_lock/scripts/vendor_poetry/migration.py
+++ b/conda_lock/scripts/vendor_poetry/migration.py
@@ -19,8 +19,10 @@
 
 directly_vendored_dependencies = get_directly_vendored_dependencies()
 
+EPOCH = 8
 
-@m.add_stage(1, "Add vendored dependency requirements to conda-lock")
+
+@m.add_stage(1 + EPOCH, "Add vendored dependency requirements to conda-lock")
 def add_vendored_requirements() -> None:
     # The list of requirements which we should add
     relevant_requirements: list[Requirement] = []
@@ -70,7 +72,7 @@ def add_vendored_requirements() -> None:
     requirements_txt_file.write_text(requirements_txt)
 
 
-@m.add_stage(2, "Update pypi_solver.py to use vendored Poetry imports")
+@m.add_stage(2 + EPOCH, "Update pypi_solver.py to use vendored Poetry imports")
 def modify_vendored_imports() -> None:
     for src_file in [get_repo_root() / "conda_lock" / "pypi_solver.py"]:
         src = src_file.read_text()
@@ -93,7 +95,9 @@ def modify_vendored_imports() -> None:
     print("Pre-commit complete. Code should be fixed now.")
 
 
-@m.add_stage(3, "Remove pexpect, requests_toolbelt, and shellingham as dependencies")
+@m.add_stage(
+    3 + EPOCH, "Remove pexpect, requests_toolbelt, and shellingham as dependencies"
+)
 def remove_unnecessary_dependencies() -> None:
     to_remove = ["pexpect", "requests-toolbelt", "shellingham"]
     subprocess.check_output(["pipreqs", str(get_vendor_root() / "poetry")])
@@ -115,7 +119,7 @@ def remove_unnecessary_dependencies() -> None:
     (get_vendor_root() / "poetry" / "requirements.txt").unlink()
 
 
-@m.add_stage(4, "Remove upper bounds on poetry dependencies")
+@m.add_stage(4 + EPOCH, "Remove upper bounds on poetry dependencies")
 def remove_upper_bounds() -> None:
     conda_lock_requirements_txt = (get_repo_root() / "requirements.txt").read_text()
     new_requirements = ""
@@ -131,14 +135,14 @@ def remove_upper_bounds() -> None:
     (get_repo_root() / "requirements.txt").write_text(new_requirements)
 
 
-@m.add_stage(5, "Use 'vendoring sync' to vendor dependencies")
+@m.add_stage(5 + EPOCH, "Use 'vendoring sync' to vendor dependencies")
 def vendor_dependencies() -> None:
     # Use the vendoring package to vendor the dependencies
     # https://pypi.org/project/vendoring/
     subprocess.check_output(["vendoring", "sync"], cwd=get_repo_root())
 
 
-@m.add_stage(6, "Delete botched license file copies")
+@m.add_stage(6 + EPOCH, "Delete botched license file copies")
 def delete_botched_license_files() -> None:
     vr = get_vendor_root()
     for license_file in chain(
@@ -147,7 +151,7 @@ def delete_botched_license_files() -> None:
         license_file.unlink()
 
 
-@m.add_stage(7, "Recreate license files")
+@m.add_stage(7 + EPOCH, "Recreate license files")
 def add_poetry_root_licenses() -> None:
     """Add the root licenses for Poetry, Poetry Core, and Cleo.
 
@@ -163,7 +167,7 @@ def add_poetry_root_licenses() -> None:
         (destination_dir / f"{dep_data.name}.LICENSE").write_text(license.text)
 
 
-@m.add_stage(8, "Describe vendored dependencies in conda-lock LICENSE")
+@m.add_stage(8 + EPOCH, "Describe vendored dependencies in conda-lock LICENSE")
 def collect_poetry_core_vendored_dependencies() -> None:
     """Collect info about poetry-core's vendored dependencies.
 
diff --git a/conda_lock/scripts/vendor_poetry/patches/poetry-core.patch b/conda_lock/scripts/vendor_poetry/patches/poetry-core.patch
deleted file mode 100644
index 53d949a47..000000000
--- a/conda_lock/scripts/vendor_poetry/patches/poetry-core.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-diff --git a/conda_lock/_vendor/poetry/core/packages/dependency.py b/conda_lock/_vendor/poetry/core/packages/dependency.py
-index 9ad16dc..6154943 100644
---- a/conda_lock/_vendor/poetry/core/packages/dependency.py
-+++ b/conda_lock/_vendor/poetry/core/packages/dependency.py
-@@ -5,8 +5,6 @@ from typing import List
- from typing import Optional
- from typing import Union
- 
--import poetry.core.packages
--
- from poetry.core.semver import Version
- from poetry.core.semver import VersionConstraint
- from poetry.core.semver import VersionRange
-@@ -25,6 +23,7 @@ from .utils.utils import convert_markers
- 
- if TYPE_CHECKING:
-     from poetry.core.version.markers import BaseMarker  # noqa
-+    from poetry.core.packages import Package  # noqa
-     from poetry.core.version.markers import VersionTypes  # noqa
- 
-     from .constraints import BaseConstraint  # noqa
-@@ -213,7 +212,7 @@ class Dependency(PackageSpecification):
-     def is_url(self):  # type: () -> bool
-         return False
- 
--    def accepts(self, package):  # type: (poetry.core.packages.Package) -> bool
-+    def accepts(self, package):  # type: (Package) -> bool
-         """
-         Determines if the given package matches this dependency.
-         """
diff --git a/conda_lock/scripts/vendor_poetry/patches/poetry.patch b/conda_lock/scripts/vendor_poetry/patches/poetry.patch
deleted file mode 100644
index 982670e53..000000000
--- a/conda_lock/scripts/vendor_poetry/patches/poetry.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-diff --git a/conda_lock/_vendor/poetry/packages/locker.py b/conda_lock/_vendor/poetry/packages/locker.py
-index bad461f..12cefb8 100644
---- a/conda_lock/_vendor/poetry/packages/locker.py
-+++ b/conda_lock/_vendor/poetry/packages/locker.py
-@@ -22,7 +22,7 @@ from tomlkit import item
- from tomlkit import table
- from tomlkit.exceptions import TOMLKitError
- 
--import poetry.repositories
-+from poetry.repositories import Repository
- 
- from poetry.core.packages import dependency_from_pep_508
- from poetry.core.packages.dependency import Dependency
-@@ -87,17 +87,17 @@ class Locker(object):
- 
-     def locked_repository(
-         self, with_dev_reqs=False
--    ):  # type: (bool) -> poetry.repositories.Repository
-+    ):  # type: (bool) -> Repository
-         """
-         Searches and returns a repository of locked packages.
-         """
-         from poetry.factory import Factory
- 
-         if not self.is_locked():
--            return poetry.repositories.Repository()
-+            return Repository()
- 
-         lock_data = self.lock_data
--        packages = poetry.repositories.Repository()
-+        packages = Repository()
- 
-         if with_dev_reqs:
-             locked_packages = lock_data["package"]