juc500 정상동작
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,160 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from argparse import SUPPRESS, ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
|
||||
from collections import OrderedDict
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Action
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
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: Any) -> None: # noqa: ANN401
|
||||
super().__init__(**kwargs)
|
||||
self._src: str | None = None
|
||||
self._sources: dict[str, str] = {}
|
||||
|
||||
def set_src(self, key: str, value: Any, src: str) -> None: # noqa: ANN401
|
||||
"""Set an option value and record where it came from.
|
||||
|
||||
:param key: the option name
|
||||
:param value: the option value
|
||||
:param src: the source of the value (e.g. ``"cli"``, ``"env var"``, ``"default"``)
|
||||
|
||||
"""
|
||||
setattr(self, key, value)
|
||||
if src.startswith("env var"):
|
||||
src = "env var"
|
||||
self._sources[key] = src
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> None: # noqa: ANN401
|
||||
if (src := getattr(self, "_src", None)) is not None:
|
||||
self._sources[key] = src
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def get_source(self, key: str) -> str | None:
|
||||
"""Return the source that provided a given option value.
|
||||
|
||||
:param key: the option name
|
||||
|
||||
:returns: the source string (e.g. ``"cli"``, ``"env var"``, ``"default"``), or ``None`` if not tracked
|
||||
|
||||
"""
|
||||
return self._sources.get(key)
|
||||
|
||||
@property
|
||||
def verbosity(self) -> int | None:
|
||||
"""The verbosity level, computed as ``verbose - quiet``, clamped to zero.
|
||||
|
||||
:returns: the verbosity level, or ``None`` if neither ``--verbose`` nor ``--quiet`` has been parsed yet
|
||||
|
||||
"""
|
||||
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: VirtualEnvOptions | None = None,
|
||||
env: Mapping[str, str] | None = None,
|
||||
*args: Any, # noqa: ANN401
|
||||
**kwargs: Any, # noqa: ANN401
|
||||
) -> 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) -> None:
|
||||
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: Action) -> None:
|
||||
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, default_source = outcome
|
||||
vars(action)["default_source"] = default_source
|
||||
else:
|
||||
outcome = action.default, "default"
|
||||
self.options.set_src(action.dest, *outcome)
|
||||
|
||||
def enable_help(self) -> None:
|
||||
self._fix_defaults()
|
||||
self.add_argument("-h", "--help", action="help", default=SUPPRESS, help="show this help message and exit")
|
||||
|
||||
def parse_known_args( # ty: ignore[invalid-method-override]
|
||||
self, args: Sequence[str] | None = None, namespace: VirtualEnvOptions | None = None
|
||||
) -> tuple[VirtualEnvOptions, list[str]]:
|
||||
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: str, **kwargs: Any) -> None: # noqa: ANN401
|
||||
super().__init__(prog, max_help_position=32, width=shutil.get_terminal_size().columns, **kwargs)
|
||||
|
||||
def _get_help_string(self, action: Action) -> str | None:
|
||||
text = super()._get_help_string(action)
|
||||
if text is not None and 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,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Action
|
||||
from typing import Any
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TypeData:
|
||||
def __init__(self, default_type: type, as_type: 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: str) -> Any: # noqa: ANN401
|
||||
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: str) -> bool:
|
||||
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: str) -> str | None:
|
||||
if not value:
|
||||
return None
|
||||
return str(value)
|
||||
|
||||
|
||||
class ListType(TypeData):
|
||||
def _validate(self) -> None:
|
||||
"""no op."""
|
||||
|
||||
def convert(self, value: str | list[str], flatten: bool = True) -> list[Any]: # 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: str | bytes | list[str]) -> list[str]:
|
||||
"""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(",") # ty: ignore[invalid-argument-type]
|
||||
values = filter(None, [x.strip() for x in values])
|
||||
else:
|
||||
values = list(value)
|
||||
|
||||
return values # ty: ignore[invalid-return-type]
|
||||
|
||||
|
||||
def convert(value: str, as_type: TypeData, source: str) -> Any: # noqa: ANN401
|
||||
"""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:
|
||||
LOGGER.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: Action) -> TypeData:
|
||||
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) # ty: ignore[invalid-argument-type]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"convert",
|
||||
"get_type",
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .convert import convert
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from .convert import TypeData
|
||||
|
||||
|
||||
def get_env_var(key: str, as_type: TypeData, env: Mapping[str, str]) -> tuple[Any, str] | None:
|
||||
"""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
|
||||
|
||||
:returns: the converted value and source, or None if not set
|
||||
|
||||
"""
|
||||
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,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
from platformdirs import user_config_dir
|
||||
|
||||
from .convert import convert
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from .convert import TypeData
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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: Mapping[str, str] | None = 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:
|
||||
LOGGER.error("failed to read config file %s because %r", config_file, exception)
|
||||
|
||||
def _load(self) -> None:
|
||||
with self.config_file.open("rt", encoding="utf-8") as file_handler:
|
||||
return self.config_parser.read_file(file_handler)
|
||||
|
||||
def get(self, key: str, as_type: TypeData) -> tuple[Any, str] | None:
|
||||
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) -> str:
|
||||
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})"
|
||||
)
|
||||
Reference in New Issue
Block a user