first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,126 @@
|
|||
from __future__ import annotations # noqa: A005
|
||||
|
||||
import os
|
||||
from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
|
||||
from collections import OrderedDict
|
||||
|
||||
from virtualenv.config.convert import get_type
|
||||
from virtualenv.config.env_var import get_env_var
|
||||
from virtualenv.config.ini import IniConfig
|
||||
|
||||
|
||||
class VirtualEnvOptions(Namespace):
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self._src = None
|
||||
self._sources = {}
|
||||
|
||||
def set_src(self, key, value, src):
|
||||
setattr(self, key, value)
|
||||
if src.startswith("env var"):
|
||||
src = "env var"
|
||||
self._sources[key] = src
|
||||
|
||||
def __setattr__(self, key, value) -> None:
|
||||
if getattr(self, "_src", None) is not None:
|
||||
self._sources[key] = self._src
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def get_source(self, key):
|
||||
return self._sources.get(key)
|
||||
|
||||
@property
|
||||
def verbosity(self):
|
||||
if not hasattr(self, "verbose") and not hasattr(self, "quiet"):
|
||||
return None
|
||||
return max(self.verbose - self.quiet, 0)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{type(self).__name__}({', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))})"
|
||||
|
||||
|
||||
class VirtualEnvConfigParser(ArgumentParser):
|
||||
"""Custom option parser which updates its defaults by checking the configuration files and environmental vars."""
|
||||
|
||||
def __init__(self, options=None, env=None, *args, **kwargs) -> None:
|
||||
env = os.environ if env is None else env
|
||||
self.file_config = IniConfig(env)
|
||||
self.epilog_list = []
|
||||
self.env = env
|
||||
kwargs["epilog"] = self.file_config.epilog
|
||||
kwargs["add_help"] = False
|
||||
kwargs["formatter_class"] = HelpFormatter
|
||||
kwargs["prog"] = "virtualenv"
|
||||
super().__init__(*args, **kwargs)
|
||||
self._fixed = set()
|
||||
if options is not None and not isinstance(options, VirtualEnvOptions):
|
||||
msg = "options must be of type VirtualEnvOptions"
|
||||
raise TypeError(msg)
|
||||
self.options = VirtualEnvOptions() if options is None else options
|
||||
self._interpreter = None
|
||||
self._app_data = None
|
||||
|
||||
def _fix_defaults(self):
|
||||
for action in self._actions:
|
||||
action_id = id(action)
|
||||
if action_id not in self._fixed:
|
||||
self._fix_default(action)
|
||||
self._fixed.add(action_id)
|
||||
|
||||
def _fix_default(self, action):
|
||||
if hasattr(action, "default") and hasattr(action, "dest") and action.default != SUPPRESS:
|
||||
as_type = get_type(action)
|
||||
names = OrderedDict((i.lstrip("-").replace("-", "_"), None) for i in action.option_strings)
|
||||
outcome = None
|
||||
for name in names:
|
||||
outcome = get_env_var(name, as_type, self.env)
|
||||
if outcome is not None:
|
||||
break
|
||||
if outcome is None and self.file_config:
|
||||
for name in names:
|
||||
outcome = self.file_config.get(name, as_type)
|
||||
if outcome is not None:
|
||||
break
|
||||
if outcome is not None:
|
||||
action.default, action.default_source = outcome
|
||||
else:
|
||||
outcome = action.default, "default"
|
||||
self.options.set_src(action.dest, *outcome)
|
||||
|
||||
def enable_help(self):
|
||||
self._fix_defaults()
|
||||
self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
|
||||
|
||||
def parse_known_args(self, args=None, namespace=None):
|
||||
if namespace is None:
|
||||
namespace = self.options
|
||||
elif namespace is not self.options:
|
||||
msg = "can only pass in parser.options"
|
||||
raise ValueError(msg)
|
||||
self._fix_defaults()
|
||||
self.options._src = "cli" # noqa: SLF001
|
||||
try:
|
||||
namespace.env = self.env
|
||||
return super().parse_known_args(args, namespace=namespace)
|
||||
finally:
|
||||
self.options._src = None # noqa: SLF001
|
||||
|
||||
|
||||
class HelpFormatter(ArgumentDefaultsHelpFormatter):
|
||||
def __init__(self, prog) -> None:
|
||||
super().__init__(prog, max_help_position=32, width=240)
|
||||
|
||||
def _get_help_string(self, action):
|
||||
text = super()._get_help_string(action)
|
||||
if hasattr(action, "default_source"):
|
||||
default = " (default: %(default)s)"
|
||||
if text.endswith(default):
|
||||
text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
|
||||
return text
|
||||
|
||||
|
||||
__all__ = [
|
||||
"HelpFormatter",
|
||||
"VirtualEnvConfigParser",
|
||||
"VirtualEnvOptions",
|
||||
]
|
|
@ -0,0 +1,100 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
class TypeData:
|
||||
def __init__(self, default_type, as_type) -> None:
|
||||
self.default_type = default_type
|
||||
self.as_type = as_type
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(base={self.default_type}, as={self.as_type})"
|
||||
|
||||
def convert(self, value):
|
||||
return self.default_type(value)
|
||||
|
||||
|
||||
class BoolType(TypeData):
|
||||
BOOLEAN_STATES: ClassVar[dict[str, bool]] = {
|
||||
"1": True,
|
||||
"yes": True,
|
||||
"true": True,
|
||||
"on": True,
|
||||
"0": False,
|
||||
"no": False,
|
||||
"false": False,
|
||||
"off": False,
|
||||
}
|
||||
|
||||
def convert(self, value):
|
||||
if value.lower() not in self.BOOLEAN_STATES:
|
||||
msg = f"Not a boolean: {value}"
|
||||
raise ValueError(msg)
|
||||
return self.BOOLEAN_STATES[value.lower()]
|
||||
|
||||
|
||||
class NoneType(TypeData):
|
||||
def convert(self, value):
|
||||
if not value:
|
||||
return None
|
||||
return str(value)
|
||||
|
||||
|
||||
class ListType(TypeData):
|
||||
def _validate(self):
|
||||
"""no op."""
|
||||
|
||||
def convert(self, value, flatten=True): # noqa: ARG002, FBT002
|
||||
values = self.split_values(value)
|
||||
result = []
|
||||
for a_value in values:
|
||||
sub_values = a_value.split(os.pathsep)
|
||||
result.extend(sub_values)
|
||||
return [self.as_type(i) for i in result]
|
||||
|
||||
def split_values(self, value):
|
||||
"""
|
||||
Split the provided value into a list.
|
||||
|
||||
First this is done by newlines. If there were no newlines in the text,
|
||||
then we next try to split by comma.
|
||||
"""
|
||||
if isinstance(value, (str, bytes)):
|
||||
# Use `splitlines` rather than a custom check for whether there is
|
||||
# more than one line. This ensures that the full `splitlines()`
|
||||
# logic is supported here.
|
||||
values = value.splitlines()
|
||||
if len(values) <= 1:
|
||||
values = value.split(",")
|
||||
values = filter(None, [x.strip() for x in values])
|
||||
else:
|
||||
values = list(value)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def convert(value, as_type, source):
|
||||
"""Convert the value as a given type where the value comes from the given source."""
|
||||
try:
|
||||
return as_type.convert(value)
|
||||
except Exception as exception:
|
||||
logging.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception)
|
||||
raise
|
||||
|
||||
|
||||
_CONVERT = {bool: BoolType, type(None): NoneType, list: ListType}
|
||||
|
||||
|
||||
def get_type(action):
|
||||
default_type = type(action.default)
|
||||
as_type = default_type if action.type is None else action.type
|
||||
return _CONVERT.get(default_type, TypeData)(default_type, as_type)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"convert",
|
||||
"get_type",
|
||||
]
|
|
@ -0,0 +1,30 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
from .convert import convert
|
||||
|
||||
|
||||
def get_env_var(key, as_type, env):
|
||||
"""
|
||||
Get the environment variable option.
|
||||
|
||||
:param key: the config key requested
|
||||
:param as_type: the type we would like to convert it to
|
||||
:param env: environment variables to use
|
||||
:return:
|
||||
"""
|
||||
environ_key = f"VIRTUALENV_{key.upper()}"
|
||||
if env.get(environ_key):
|
||||
value = env[environ_key]
|
||||
|
||||
with suppress(Exception): # note the converter already logs a warning when failures happen
|
||||
source = f"env var {environ_key}"
|
||||
as_type = convert(value, as_type, source)
|
||||
return as_type, source
|
||||
return None
|
||||
|
||||
|
||||
__all__ = [
|
||||
"get_env_var",
|
||||
]
|
|
@ -0,0 +1,75 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from typing import ClassVar
|
||||
|
||||
from platformdirs import user_config_dir
|
||||
|
||||
from .convert import convert
|
||||
|
||||
|
||||
class IniConfig:
|
||||
VIRTUALENV_CONFIG_FILE_ENV_VAR: ClassVar[str] = "VIRTUALENV_CONFIG_FILE"
|
||||
STATE: ClassVar[dict[bool | None, str]] = {None: "failed to parse", True: "active", False: "missing"}
|
||||
|
||||
section = "virtualenv"
|
||||
|
||||
def __init__(self, env=None) -> None:
|
||||
env = os.environ if env is None else env
|
||||
config_file = env.get(self.VIRTUALENV_CONFIG_FILE_ENV_VAR, None)
|
||||
self.is_env_var = config_file is not None
|
||||
if config_file is None:
|
||||
config_file = Path(user_config_dir(appname="virtualenv", appauthor="pypa")) / "virtualenv.ini"
|
||||
else:
|
||||
config_file = Path(config_file)
|
||||
self.config_file = config_file
|
||||
self._cache = {}
|
||||
|
||||
exception = None
|
||||
self.has_config_file = None
|
||||
try:
|
||||
self.has_config_file = self.config_file.exists()
|
||||
except OSError as exc:
|
||||
exception = exc
|
||||
else:
|
||||
if self.has_config_file:
|
||||
self.config_file = self.config_file.resolve()
|
||||
self.config_parser = ConfigParser()
|
||||
try:
|
||||
self._load()
|
||||
self.has_virtualenv_section = self.config_parser.has_section(self.section)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
exception = exc
|
||||
if exception is not None:
|
||||
logging.error("failed to read config file %s because %r", config_file, exception)
|
||||
|
||||
def _load(self):
|
||||
with self.config_file.open("rt", encoding="utf-8") as file_handler:
|
||||
return self.config_parser.read_file(file_handler)
|
||||
|
||||
def get(self, key, as_type):
|
||||
cache_key = key, as_type
|
||||
if cache_key in self._cache:
|
||||
return self._cache[cache_key]
|
||||
try:
|
||||
source = "file"
|
||||
raw_value = self.config_parser.get(self.section, key.lower())
|
||||
value = convert(raw_value, as_type, source)
|
||||
result = value, source
|
||||
except Exception: # noqa: BLE001
|
||||
result = None
|
||||
self._cache[cache_key] = result
|
||||
return result
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.has_config_file) and bool(self.has_virtualenv_section)
|
||||
|
||||
@property
|
||||
def epilog(self):
|
||||
return (
|
||||
f"\nconfig file {self.config_file} {self.STATE[self.has_config_file]} "
|
||||
f"(change{'d' if self.is_env_var else ''} via env var {self.VIRTUALENV_CONFIG_FILE_ENV_VAR})"
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue