Vendor in latest packages available (#5657)

* Update to the latest vendoring package versions available.

* add news fragment

* fix vendoring CI
This commit is contained in:
Matt Davis
2023-04-18 09:34:41 -04:00
committed by GitHub
parent 4ce2d98cfd
commit 740c3c0865
54 changed files with 2251 additions and 1348 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
with:
api_key: ${{ secrets.FORESIGHT_API_KEY }}
- run: |
python -m pip install --upgrade wheel invoke parver beautifulsoup4 vistir towncrier requests parse
python -m pip install --upgrade wheel invoke parver beautifulsoup4 vistir towncrier requests parse hatch-fancy-pypi-readme
python -m invoke vendoring.update
tests:
name: ${{matrix.os}} / ${{ matrix.python-version }}
+1
View File
@@ -0,0 +1 @@
Vendor in latest available dependencies: ``attrs==23.1.0`` ``click-didyoumean==0.3.0`` ``click==8.1.3`` ``markupsafe==2.1.2`` ``pipdeptree==2.7.0`` ``shellingham==1.5.0.post1`` ``tomlkit==0.11.7``
+74 -21
View File
@@ -1,9 +1,11 @@
# SPDX-License-Identifier: MIT
import sys
"""
Classes Without Boilerplate
"""
from functools import partial
from typing import Callable
from . import converters, exceptions, filters, setters, validators
from ._cmp import cmp_using
@@ -20,31 +22,22 @@ from ._make import (
make_class,
validate,
)
from ._next_gen import define, field, frozen, mutable
from ._version_info import VersionInfo
__version__ = "22.1.0"
__version_info__ = VersionInfo._from_version_string(__version__)
__title__ = "attrs"
__description__ = "Classes Without Boilerplate"
__url__ = "https://www.attrs.org/"
__uri__ = __url__
__doc__ = __description__ + " <" + __uri__ + ">"
__author__ = "Hynek Schlawack"
__email__ = "hs@ox.cx"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
s = attributes = attrs
ib = attr = attrib
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
class AttrsInstance:
pass
__all__ = [
"Attribute",
"AttrsInstance",
"Factory",
"NOTHING",
"asdict",
@@ -56,15 +49,19 @@ __all__ = [
"attrs",
"cmp_using",
"converters",
"define",
"evolve",
"exceptions",
"field",
"fields",
"fields_dict",
"filters",
"frozen",
"get_run_validators",
"has",
"ib",
"make_class",
"mutable",
"resolve_types",
"s",
"set_run_validators",
@@ -73,7 +70,63 @@ __all__ = [
"validators",
]
if sys.version_info[:2] >= (3, 6):
from ._next_gen import define, field, frozen, mutable # noqa: F401
__all__.extend(("define", "field", "frozen", "mutable"))
def _make_getattr(mod_name: str) -> Callable:
"""
Create a metadata proxy for packaging information that uses *mod_name* in
its warnings and errors.
"""
def __getattr__(name: str) -> str:
dunder_to_metadata = {
"__title__": "Name",
"__copyright__": "",
"__version__": "version",
"__version_info__": "version",
"__description__": "summary",
"__uri__": "",
"__url__": "",
"__author__": "",
"__email__": "",
"__license__": "license",
}
if name not in dunder_to_metadata.keys():
raise AttributeError(f"module {mod_name} has no attribute {name}")
import sys
import warnings
if sys.version_info < (3, 8):
from importlib_metadata import metadata
else:
from importlib.metadata import metadata
if name != "__version_info__":
warnings.warn(
f"Accessing {mod_name}.{name} is deprecated and will be "
"removed in a future release. Use importlib.metadata directly "
"to query for attrs's packaging metadata.",
DeprecationWarning,
stacklevel=2,
)
meta = metadata("attrs")
if name == "__license__":
return "MIT"
elif name == "__copyright__":
return "Copyright (c) 2015 Hynek Schlawack"
elif name in ("__uri__", "__url__"):
return meta["Project-URL"].split(" ", 1)[-1]
elif name == "__version_info__":
return VersionInfo._from_version_string(meta["version"])
elif name == "__author__":
return meta["Author-email"].rsplit(" ", 1)[0]
elif name == "__email__":
return meta["Author-email"].rsplit("<", 1)[1][:-1]
return meta[dunder_to_metadata[name]]
return __getattr__
__getattr__ = _make_getattr(__name__)
+98 -13
View File
@@ -1,9 +1,9 @@
import enum
import sys
from typing import (
Any,
Callable,
ClassVar,
Dict,
Generic,
List,
@@ -25,8 +25,14 @@ from . import filters as filters
from . import setters as setters
from . import validators as validators
from ._cmp import cmp_using as cmp_using
from ._typing_compat import AttrsInstance_
from ._version_info import VersionInfo
if sys.version_info >= (3, 10):
from typing import TypeGuard
else:
from typing_extensions import TypeGuard
__version__: str
__version_info__: VersionInfo
__title__: str
@@ -42,30 +48,34 @@ _T = TypeVar("_T")
_C = TypeVar("_C", bound=type)
_EqOrderType = Union[bool, Callable[[Any], Any]]
_ValidatorType = Callable[[Any, Attribute[_T], _T], Any]
_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any]
_ConverterType = Callable[[Any], Any]
_FilterType = Callable[[Attribute[_T], _T], bool]
_FilterType = Callable[["Attribute[_T]", _T], bool]
_ReprType = Callable[[Any], str]
_ReprArgType = Union[bool, _ReprType]
_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any]
_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any]
_OnSetAttrArgType = Union[
_OnSetAttrType, List[_OnSetAttrType], setters._NoOpType
]
_FieldTransformer = Callable[
[type, List[Attribute[Any]]], List[Attribute[Any]]
[type, List["Attribute[Any]"]], List["Attribute[Any]"]
]
# FIXME: in reality, if multiple validators are passed they must be in a list
# or tuple, but those are invariant and so would prevent subtypes of
# _ValidatorType from working when passed in a list or tuple.
_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]]
# A protocol to be able to statically accept an attrs class.
class AttrsInstance(Protocol):
__attrs_attrs__: ClassVar[Any]
# We subclass this here to keep the protocol's qualified name clean.
class AttrsInstance(AttrsInstance_, Protocol):
pass
_A = TypeVar("_A", bound=AttrsInstance)
# _make --
NOTHING: object
class _Nothing(enum.Enum):
NOTHING = enum.auto()
NOTHING = _Nothing.NOTHING
# NOTE: Factory lies about its return type to make this possible:
# `x: List[int] # = Factory(list)`
@@ -107,6 +117,7 @@ def __dataclass_transform__(
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
frozen_default: bool = False,
field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()),
) -> Callable[[_T], _T]: ...
@@ -125,6 +136,8 @@ class Attribute(Generic[_T]):
type: Optional[Type[_T]]
kw_only: bool
on_setattr: _OnSetAttrType
alias: Optional[str]
def evolve(self, **changes: Any) -> "Attribute[Any]": ...
# NOTE: We had several choices for the annotation to use for type arg:
@@ -167,6 +180,7 @@ def attrib(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> Any: ...
# This form catches an explicit None or no default and infers the type from the
@@ -187,6 +201,7 @@ def attrib(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> _T: ...
# This form catches an explicit default argument.
@@ -206,6 +221,7 @@ def attrib(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any
@@ -225,6 +241,7 @@ def attrib(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> Any: ...
@overload
def field(
@@ -241,6 +258,8 @@ def field(
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> Any: ...
# This form catches an explicit None or no default and infers the type from the
@@ -260,6 +279,8 @@ def field(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> _T: ...
# This form catches an explicit default argument.
@@ -278,6 +299,8 @@ def field(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any
@@ -296,6 +319,8 @@ def field(
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
type: Optional[type] = ...,
) -> Any: ...
@overload
@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field))
@@ -323,6 +348,7 @@ def attrs(
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
match_args: bool = ...,
unsafe_hash: Optional[bool] = ...,
) -> _C: ...
@overload
@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field))
@@ -350,6 +376,7 @@ def attrs(
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
match_args: bool = ...,
unsafe_hash: Optional[bool] = ...,
) -> Callable[[_C], _C]: ...
@overload
@__dataclass_transform__(field_descriptors=(attrib, field))
@@ -358,6 +385,7 @@ def define(
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
unsafe_hash: Optional[bool] = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
@@ -383,6 +411,7 @@ def define(
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
unsafe_hash: Optional[bool] = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
@@ -403,17 +432,73 @@ def define(
) -> Callable[[_C], _C]: ...
mutable = define
frozen = define # they differ only in their defaults
@overload
@__dataclass_transform__(
frozen_default=True, field_descriptors=(attrib, field)
)
def frozen(
maybe_cls: _C,
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
unsafe_hash: Optional[bool] = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
weakref_slot: bool = ...,
str: bool = ...,
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
match_args: bool = ...,
) -> _C: ...
@overload
@__dataclass_transform__(
frozen_default=True, field_descriptors=(attrib, field)
)
def frozen(
maybe_cls: None = ...,
*,
these: Optional[Dict[str, Any]] = ...,
repr: bool = ...,
unsafe_hash: Optional[bool] = ...,
hash: Optional[bool] = ...,
init: bool = ...,
slots: bool = ...,
frozen: bool = ...,
weakref_slot: bool = ...,
str: bool = ...,
auto_attribs: bool = ...,
kw_only: bool = ...,
cache_hash: bool = ...,
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
match_args: bool = ...,
) -> Callable[[_C], _C]: ...
def fields(cls: Type[AttrsInstance]) -> Any: ...
def fields_dict(cls: Type[AttrsInstance]) -> Dict[str, Attribute[Any]]: ...
def validate(inst: AttrsInstance) -> None: ...
def resolve_types(
cls: _C,
cls: _A,
globalns: Optional[Dict[str, Any]] = ...,
localns: Optional[Dict[str, Any]] = ...,
attribs: Optional[List[Attribute[Any]]] = ...,
) -> _C: ...
include_extras: bool = ...,
) -> _A: ...
# TODO: add support for returning a proper attrs class from the mypy plugin
# we use Any instead of _CountingAttr so that e.g. `make_class('Foo',
@@ -470,7 +555,7 @@ def astuple(
tuple_factory: Type[Sequence[Any]] = ...,
retain_collection_types: bool = ...,
) -> Tuple[Any, ...]: ...
def has(cls: type) -> bool: ...
def has(cls: type) -> TypeGuard[Type[AttrsInstance]]: ...
def assoc(inst: _T, **changes: Any) -> _T: ...
def evolve(inst: _T, **changes: Any) -> _T: ...
+17 -17
View File
@@ -20,22 +20,22 @@ def cmp_using(
class_name="Comparable",
):
"""
Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and
``cmp`` arguments to customize field comparison.
Create a class that can be passed into `attrs.field`'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.
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 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.
@@ -130,9 +130,9 @@ def _make_operator(name, func):
return result
method.__name__ = "__%s__" % (name,)
method.__doc__ = "Return a %s b. Computed by attrs." % (
_operation_names[name],
method.__name__ = f"__{name}__"
method.__doc__ = (
f"Return a {_operation_names[name]} b. Computed by attrs."
)
return method
+7 -7
View File
@@ -3,11 +3,11 @@ from typing import Any, Callable, Optional, Type
_CompareWithType = Callable[[Any, Any], bool]
def cmp_using(
eq: Optional[_CompareWithType],
lt: Optional[_CompareWithType],
le: Optional[_CompareWithType],
gt: Optional[_CompareWithType],
ge: Optional[_CompareWithType],
require_same_type: bool,
class_name: str,
eq: Optional[_CompareWithType] = ...,
lt: Optional[_CompareWithType] = ...,
le: Optional[_CompareWithType] = ...,
gt: Optional[_CompareWithType] = ...,
ge: Optional[_CompareWithType] = ...,
require_same_type: bool = ...,
class_name: str = ...,
) -> Type: ...
+29 -29
View File
@@ -9,20 +9,13 @@ import types
import warnings
from collections.abc import Mapping, Sequence # noqa
from typing import _GenericAlias
PYPY = platform.python_implementation() == "PyPy"
PY36 = sys.version_info[:2] >= (3, 6)
HAS_F_STRINGS = PY36
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
PY310 = sys.version_info[:2] >= (3, 10)
if PYPY or PY36:
ordered_dict = dict
else:
from collections import OrderedDict
ordered_dict = OrderedDict
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
def just_warn(*args, **kw):
@@ -90,32 +83,32 @@ def make_set_closure_cell():
# Otherwise gotta do it the hard way.
# Create a function that will set its first cellvar to `value`.
def set_first_cellvar_to(value):
x = value
return
# This function will be eliminated as dead code, but
# not before its reference to `x` forces `x` to be
# represented as a closure cell rather than a local.
def force_x_to_be_a_cell(): # pragma: no cover
return x
try:
# Extract the code object and make sure our assumptions about
# the closure behavior are correct.
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):
def set_closure_cell(cell, value):
cell.cell_contents = value
else:
# Create a function that will set its first cellvar to `value`.
def set_first_cellvar_to(value):
x = value
return
# This function will be eliminated as dead code, but
# not before its reference to `x` forces `x` to be
# represented as a closure cell rather than a local.
def force_x_to_be_a_cell(): # pragma: no cover
return x
# Extract the code object and make sure our assumptions about
# the closure behavior are correct.
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.
args = [co.co_argcount]
args.append(co.co_kwonlyargcount)
args.extend(
@@ -183,3 +176,10 @@ set_closure_cell = make_set_closure_cell()
# 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()
def get_generic_base(cl):
"""If this is a generic class (A[str]), return the generic base for it."""
if cl.__class__ is _GenericAlias:
return cl.__origin__
return None
+93 -36
View File
@@ -3,6 +3,7 @@
import copy
from ._compat import PY_3_9_PLUS, get_generic_base
from ._make import NOTHING, _obj_setattr, fields
from .exceptions import AttrsAttributeNotFoundError
@@ -16,13 +17,13 @@ def asdict(
value_serializer=None,
):
"""
Return the ``attrs`` attribute values of *inst* as a dict.
Return the *attrs* attribute values of *inst* as a dict.
Optionally recurse into other ``attrs``-decorated classes.
Optionally recurse into other *attrs*-decorated classes.
:param inst: Instance of an ``attrs``-decorated class.
:param inst: Instance of an *attrs*-decorated class.
:param bool recurse: Recurse into classes that are also
``attrs``-decorated.
*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 `attrs.Attribute` as the first argument and the
@@ -40,7 +41,7 @@ def asdict(
:rtype: return type of *dict_factory*
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class.
.. versionadded:: 16.0.0 *dict_factory*
@@ -195,13 +196,13 @@ def astuple(
retain_collection_types=False,
):
"""
Return the ``attrs`` attribute values of *inst* as a tuple.
Return the *attrs* attribute values of *inst* as a tuple.
Optionally recurse into other ``attrs``-decorated classes.
Optionally recurse into other *attrs*-decorated classes.
:param inst: Instance of an ``attrs``-decorated class.
:param inst: Instance of an *attrs*-decorated class.
:param bool recurse: Recurse into classes that are also
``attrs``-decorated.
*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 `attrs.Attribute` as the first argument and the
@@ -215,7 +216,7 @@ def astuple(
:rtype: return type of *tuple_factory*
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class.
.. versionadded:: 16.2.0
@@ -289,28 +290,48 @@ def astuple(
def has(cls):
"""
Check whether *cls* is a class with ``attrs`` attributes.
Check whether *cls* is a class with *attrs* attributes.
:param type cls: Class to introspect.
:raise TypeError: If *cls* is not a class.
:rtype: bool
"""
return getattr(cls, "__attrs_attrs__", None) is not None
attrs = getattr(cls, "__attrs_attrs__", None)
if attrs is not None:
return True
# No attrs, maybe it's a specialized generic (A[str])?
generic_base = get_generic_base(cls)
if generic_base is not None:
generic_attrs = getattr(generic_base, "__attrs_attrs__", None)
if generic_attrs is not None:
# Stick it on here for speed next time.
cls.__attrs_attrs__ = generic_attrs
return generic_attrs is not None
return False
def assoc(inst, **changes):
"""
Copy *inst* and apply *changes*.
:param inst: Instance of a class with ``attrs`` attributes.
This is different from `evolve` that applies the changes to the arguments
that create the new instance.
`evolve`'s behavior is preferable, but there are `edge cases`_ where it
doesn't work. Therefore `assoc` is deprecated, but will not be removed.
.. _`edge cases`: https://github.com/python-attrs/attrs/issues/251
:param inst: Instance of a class with *attrs* attributes.
:param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated.
:raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
be found on *cls*.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.AttrsAttributeNotFoundError: If *attr_name*
couldn't be found on *cls*.
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class.
.. deprecated:: 17.1.0
@@ -318,57 +339,83 @@ def assoc(inst, **changes):
This function will not be removed du to the slightly different approach
compared to `attrs.evolve`.
"""
import warnings
warnings.warn(
"assoc is deprecated and will be removed after 2018/01.",
DeprecationWarning,
stacklevel=2,
)
new = copy.copy(inst)
attrs = fields(inst.__class__)
for k, v in changes.items():
a = getattr(attrs, k, NOTHING)
if a is NOTHING:
raise AttrsAttributeNotFoundError(
"{k} is not an attrs attribute on {cl}.".format(
k=k, cl=new.__class__
)
f"{k} is not an attrs attribute on {new.__class__}."
)
_obj_setattr(new, k, v)
return new
def evolve(inst, **changes):
def evolve(*args, **changes):
"""
Create a new instance, based on *inst* with *changes* applied.
Create a new instance, based on the first positional argument with
*changes* applied.
:param inst: Instance of a class with ``attrs`` attributes.
:param inst: Instance of a class with *attrs* attributes.
:param changes: Keyword changes in the new copy.
:return: A copy of inst with *changes* incorporated.
:raise TypeError: If *attr_name* couldn't be found in the class
``__init__``.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class.
.. versionadded:: 17.1.0
.. versionadded:: 17.1.0
.. deprecated:: 23.1.0
It is now deprecated to pass the instance using the keyword argument
*inst*. It will raise a warning until at least April 2024, after which
it will become an error. Always pass the instance as a positional
argument.
"""
# Try to get instance by positional argument first.
# Use changes otherwise and warn it'll break.
if args:
try:
(inst,) = args
except ValueError:
raise TypeError(
f"evolve() takes 1 positional argument, but {len(args)} "
"were given"
) from None
else:
try:
inst = changes.pop("inst")
except KeyError:
raise TypeError(
"evolve() missing 1 required positional argument: 'inst'"
) from None
import warnings
warnings.warn(
"Passing the instance per keyword argument is deprecated and "
"will stop working in, or after, April 2024.",
DeprecationWarning,
stacklevel=2,
)
cls = inst.__class__
attrs = fields(cls)
for a in attrs:
if not a.init:
continue
attr_name = a.name # To deal with private attributes.
init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
init_name = a.alias
if init_name not in changes:
changes[init_name] = getattr(inst, attr_name)
return cls(**changes)
def resolve_types(cls, globalns=None, localns=None, attribs=None):
def resolve_types(
cls, globalns=None, localns=None, attribs=None, include_extras=True
):
"""
Resolve any strings and forward annotations in type annotations.
@@ -387,10 +434,14 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
: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.
since *cls* is not an *attrs* class yet.
:param bool include_extras: Resolve more accurately, if possible.
Pass ``include_extras`` to ``typing.get_hints``, if supported by the
typing module. On supported Python versions (3.9+), this resolves the
types more accurately.
:raise TypeError: If *cls* is not a class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
:raise attrs.exceptions.NotAnAttrsClassError: If *cls* is not an *attrs*
class and you didn't pass any attribs.
:raise NameError: If types cannot be resolved because of missing variables.
@@ -400,6 +451,7 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
.. versionadded:: 20.1.0
.. versionadded:: 21.1.0 *attribs*
.. versionadded:: 23.1.0 *include_extras*
"""
# Since calling get_type_hints is expensive we cache whether we've
@@ -407,7 +459,12 @@ def resolve_types(cls, globalns=None, localns=None, attribs=None):
if getattr(cls, "__attrs_types_resolved__", None) != cls:
import typing
hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
kwargs = {"globalns": globalns, "localns": localns}
if PY_3_9_PLUS:
kwargs["include_extras"] = include_extras
hints = typing.get_type_hints(cls, **kwargs)
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.
+311 -330
View File
File diff suppressed because it is too large Load Diff
+15 -3
View File
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: MIT
"""
These are Python 3.6+-only and keyword-only APIs that call `attr.s` and
`attr.ib` with different default values.
These are keyword-only APIs that call `attr.s` and `attr.ib` with different
default values.
"""
@@ -26,6 +26,7 @@ def define(
*,
these=None,
repr=None,
unsafe_hash=None,
hash=None,
init=None,
slots=True,
@@ -45,7 +46,7 @@ def define(
match_args=True,
):
r"""
Define an ``attrs`` class.
Define an *attrs* class.
Differences to the classic `attr.s` that it uses underneath:
@@ -81,6 +82,8 @@ def define(
.. versionadded:: 20.1.0
.. versionchanged:: 21.3.0 Converters are also run ``on_setattr``.
.. versionadded:: 22.2.0
*unsafe_hash* as an alias for *hash* (for :pep:`681` compliance).
"""
def do_it(cls, auto_attribs):
@@ -89,6 +92,7 @@ def define(
these=these,
repr=repr,
hash=hash,
unsafe_hash=unsafe_hash,
init=init,
slots=slots,
frozen=frozen,
@@ -163,17 +167,23 @@ def field(
hash=None,
init=True,
metadata=None,
type=None,
converter=None,
factory=None,
kw_only=False,
eq=None,
order=None,
on_setattr=None,
alias=None,
):
"""
Identical to `attr.ib`, except keyword-only and with some arguments
removed.
.. versionadded:: 23.1.0
The *type* parameter has been re-added; mostly for
{func}`attrs.make_class`. Please note that type checkers ignore this
metadata.
.. versionadded:: 20.1.0
"""
return attrib(
@@ -183,12 +193,14 @@ def field(
hash=hash,
init=init,
metadata=metadata,
type=type,
converter=converter,
factory=factory,
kw_only=kw_only,
eq=eq,
order=order,
on_setattr=on_setattr,
alias=alias,
)
+15
View File
@@ -0,0 +1,15 @@
from typing import Any, ClassVar, Protocol
# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`.
MYPY = False
if MYPY:
# A protocol to be able to statically accept an attrs class.
class AttrsInstance_(Protocol):
__attrs_attrs__: ClassVar[Any]
else:
# For type checkers without plug-in support use an empty protocol that
# will (hopefully) be combined into a union.
class AttrsInstance_(Protocol):
pass
+1 -1
View File
@@ -141,4 +141,4 @@ def to_bool(val):
except TypeError:
# Raised when "val" is not hashable (e.g., lists)
pass
raise ValueError("Cannot convert value to bool: {}".format(val))
raise ValueError(f"Cannot convert value to bool: {val}")
+1 -1
View File
@@ -1,4 +1,4 @@
from typing import Callable, Optional, TypeVar, overload
from typing import Callable, TypeVar, overload
from . import _ConverterType
+7 -8
View File
@@ -34,7 +34,7 @@ class FrozenAttributeError(FrozenError):
class AttrsAttributeNotFoundError(ValueError):
"""
An ``attrs`` function couldn't find an attribute that the user asked for.
An *attrs* function couldn't find an attribute that the user asked for.
.. versionadded:: 16.2.0
"""
@@ -42,7 +42,7 @@ class AttrsAttributeNotFoundError(ValueError):
class NotAnAttrsClassError(ValueError):
"""
A non-``attrs`` class has been passed into an ``attrs`` function.
A non-*attrs* class has been passed into an *attrs* function.
.. versionadded:: 16.2.0
"""
@@ -50,7 +50,7 @@ class NotAnAttrsClassError(ValueError):
class DefaultAlreadySetError(RuntimeError):
"""
A default has been set using ``attr.ib()`` and is attempted to be reset
A default has been set when defining the field and is attempted to be reset
using the decorator.
.. versionadded:: 17.1.0
@@ -59,8 +59,7 @@ class DefaultAlreadySetError(RuntimeError):
class UnannotatedAttributeError(RuntimeError):
"""
A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type
annotation.
A class with ``auto_attribs=True`` has a field without a type annotation.
.. versionadded:: 17.3.0
"""
@@ -68,7 +67,7 @@ class UnannotatedAttributeError(RuntimeError):
class PythonTooOldError(RuntimeError):
"""
It was attempted to use an ``attrs`` feature that requires a newer Python
It was attempted to use an *attrs* feature that requires a newer Python
version.
.. versionadded:: 18.2.0
@@ -77,8 +76,8 @@ class PythonTooOldError(RuntimeError):
class NotCallableError(TypeError):
"""
A ``attr.ib()`` requiring a callable has been set with a value
that is not callable.
A field requiring a callable has been set with a value that is not
callable.
.. versionadded:: 19.2.0
"""
+21 -6
View File
@@ -13,6 +13,7 @@ def _split_what(what):
"""
return (
frozenset(cls for cls in what if isinstance(cls, type)),
frozenset(cls for cls in what if isinstance(cls, str)),
frozenset(cls for cls in what if isinstance(cls, Attribute)),
)
@@ -22,14 +23,21 @@ def include(*what):
Include *what*.
:param what: What to include.
:type what: `list` of `type` or `attrs.Attribute`\\ s
:type what: `list` of classes `type`, field names `str` or
`attrs.Attribute`\\ s
:rtype: `callable`
.. versionchanged:: 23.1.0 Accept strings with field names.
"""
cls, attrs = _split_what(what)
cls, names, attrs = _split_what(what)
def include_(attribute, value):
return value.__class__ in cls or attribute in attrs
return (
value.__class__ in cls
or attribute.name in names
or attribute in attrs
)
return include_
@@ -39,13 +47,20 @@ def exclude(*what):
Exclude *what*.
:param what: What to exclude.
:type what: `list` of classes or `attrs.Attribute`\\ s.
:type what: `list` of classes `type`, field names `str` or
`attrs.Attribute`\\ s.
:rtype: `callable`
.. versionchanged:: 23.3.0 Accept field name string as input argument
"""
cls, attrs = _split_what(what)
cls, names, attrs = _split_what(what)
def exclude_(attribute, value):
return value.__class__ not in cls and attribute not in attrs
return not (
value.__class__ in cls
or attribute.name in names
or attribute in attrs
)
return exclude_
+2 -2
View File
@@ -2,5 +2,5 @@ from typing import Any, Union
from . import Attribute, _FilterType
def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ...
def include(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ...
def exclude(*what: Union[type, str, Attribute[Any]]) -> _FilterType[Any]: ...
+1 -1
View File
@@ -1,4 +1,4 @@
from typing import Any, NewType, NoReturn, TypeVar, cast
from typing import Any, NewType, NoReturn, TypeVar
from . import Attribute, _OnSetAttrType
+142 -16
View File
@@ -9,18 +9,14 @@ import operator
import re
from contextlib import contextmanager
from re import Pattern
from ._config import get_run_validators, set_run_validators
from ._make import _AndValidator, and_, attrib, attrs
from .converters import default_if_none
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",
@@ -37,6 +33,7 @@ __all__ = [
"matches_re",
"max_len",
"min_len",
"not_",
"optional",
"provides",
"set_disabled",
@@ -126,7 +123,7 @@ def instance_of(type):
`isinstance` therefore it's also valid to pass a tuple of types).
:param type: The type to check for.
:type type: type or tuple of types
:type type: type or tuple of type
:raises TypeError: With a human readable error message, the attribute
(of type `attrs.Attribute`), the expected type, and the value it
@@ -247,7 +244,17 @@ def provides(interface):
:raises TypeError: With a human readable error message, the attribute
(of type `attrs.Attribute`), the expected interface, and the
value it got.
.. deprecated:: 23.1.0
"""
import warnings
warnings.warn(
"attrs's zope-interface support is deprecated and will be removed in, "
"or after, April 2024.",
DeprecationWarning,
stacklevel=2,
)
return _ProvidesValidator(interface)
@@ -273,15 +280,16 @@ def optional(validator):
which can be set to ``None`` in addition to satisfying the requirements of
the sub-validator.
:param validator: A validator (or a list of validators) that is used for
non-``None`` values.
:type validator: callable or `list` of callables.
:param Callable | tuple[Callable] | list[Callable] validator: A validator
(or validators) that is used for non-``None`` values.
.. versionadded:: 15.1.0
.. versionchanged:: 17.1.0 *validator* can be a list of validators.
.. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
"""
if isinstance(validator, list):
if isinstance(validator, (list, tuple)):
return _OptionalValidator(_AndValidator(validator))
return _OptionalValidator(validator)
@@ -357,13 +365,13 @@ class _IsCallableValidator:
def is_callable():
"""
A validator that raises a `attr.exceptions.NotCallableError` if the
A validator that raises a `attrs.exceptions.NotCallableError` if the
initializer is called with a value for this particular attribute
that is not callable.
.. versionadded:: 19.1.0
:raises `attr.exceptions.NotCallableError`: With a human readable error
:raises attrs.exceptions.NotCallableError: With a human readable error
message containing the attribute (`attrs.Attribute`) name,
and the value it got.
"""
@@ -391,7 +399,7 @@ class _DeepIterable:
iterable_identifier = (
""
if self.iterable_validator is None
else " {iterable!r}".format(iterable=self.iterable_validator)
else f" {self.iterable_validator!r}"
)
return (
"<deep_iterable validator for{iterable_identifier}"
@@ -548,7 +556,7 @@ class _MaxLengthValidator:
)
def __repr__(self):
return "<max_len validator for {max}>".format(max=self.max_length)
return f"<max_len validator for {self.max_length}>"
def max_len(length):
@@ -579,7 +587,7 @@ class _MinLengthValidator:
)
def __repr__(self):
return "<min_len validator for {min}>".format(min=self.min_length)
return f"<min_len validator for {self.min_length}>"
def min_len(length):
@@ -592,3 +600,121 @@ def min_len(length):
.. versionadded:: 22.1.0
"""
return _MinLengthValidator(length)
@attrs(repr=False, slots=True, hash=True)
class _SubclassOfValidator:
type = attrib()
def __call__(self, inst, attr, value):
"""
We use a callable class to be able to change the ``__repr__``.
"""
if not issubclass(value, self.type):
raise TypeError(
"'{name}' must be a subclass of {type!r} "
"(got {value!r}).".format(
name=attr.name,
type=self.type,
value=value,
),
attr,
self.type,
value,
)
def __repr__(self):
return "<subclass_of validator for type {type!r}>".format(
type=self.type
)
def _subclass_of(type):
"""
A validator that raises a `TypeError` if the initializer is called
with a wrong type for this particular attribute (checks are performed using
`issubclass` therefore it's also valid to pass a tuple of types).
:param type: The type to check for.
:type type: type or tuple of types
:raises TypeError: With a human readable error message, the attribute
(of type `attrs.Attribute`), the expected type, and the value it
got.
"""
return _SubclassOfValidator(type)
@attrs(repr=False, slots=True, hash=True)
class _NotValidator:
validator = attrib()
msg = attrib(
converter=default_if_none(
"not_ validator child '{validator!r}' "
"did not raise a captured error"
)
)
exc_types = attrib(
validator=deep_iterable(
member_validator=_subclass_of(Exception),
iterable_validator=instance_of(tuple),
),
)
def __call__(self, inst, attr, value):
try:
self.validator(inst, attr, value)
except self.exc_types:
pass # suppress error to invert validity
else:
raise ValueError(
self.msg.format(
validator=self.validator,
exc_types=self.exc_types,
),
attr,
self.validator,
value,
self.exc_types,
)
def __repr__(self):
return (
"<not_ validator wrapping {what!r}, " "capturing {exc_types!r}>"
).format(
what=self.validator,
exc_types=self.exc_types,
)
def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
"""
A validator that wraps and logically 'inverts' the validator passed to it.
It will raise a `ValueError` if the provided validator *doesn't* raise a
`ValueError` or `TypeError` (by default), and will suppress the exception
if the provided validator *does*.
Intended to be used with existing validators to compose logic without
needing to create inverted variants, for example, ``not_(in_(...))``.
:param validator: A validator to be logically inverted.
:param msg: Message to raise if validator fails.
Formatted with keys ``exc_types`` and ``validator``.
:type msg: str
:param exc_types: Exception type(s) to capture.
Other types raised by child validators will not be intercepted and
pass through.
:raises ValueError: With a human readable error message,
the attribute (of type `attrs.Attribute`),
the validator that failed to raise an exception,
the value it got,
and the expected exception types.
.. versionadded:: 22.2.0
"""
try:
exc_types = tuple(exc_types)
except TypeError:
exc_types = (exc_types,)
return _NotValidator(validator, msg, exc_types)
+9 -1
View File
@@ -51,7 +51,9 @@ def instance_of(
def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ...
def provides(interface: Any) -> _ValidatorType[Any]: ...
def optional(
validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]]
validator: Union[
_ValidatorType[_T], List[_ValidatorType[_T]], Tuple[_ValidatorType[_T]]
]
) -> _ValidatorType[Optional[_T]]: ...
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
@@ -78,3 +80,9 @@ def ge(val: _T) -> _ValidatorType[_T]: ...
def gt(val: _T) -> _ValidatorType[_T]: ...
def max_len(length: int) -> _ValidatorType[_T]: ...
def min_len(length: int) -> _ValidatorType[_T]: ...
def not_(
validator: _ValidatorType[_T],
*,
msg: Optional[str] = None,
exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ...,
) -> _ValidatorType[_T]: ...
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
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
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.
+5 -10
View File
@@ -3,17 +3,9 @@
from pipenv.vendor.attr import (
NOTHING,
Attribute,
AttrsInstance,
Factory,
__author__,
__copyright__,
__description__,
__doc__,
__email__,
__license__,
__title__,
__url__,
__version__,
__version_info__,
_make_getattr,
assoc,
cmp_using,
define,
@@ -48,6 +40,7 @@ __all__ = [
"assoc",
"astuple",
"Attribute",
"AttrsInstance",
"cmp_using",
"converters",
"define",
@@ -68,3 +61,5 @@ __all__ = [
"validate",
"validators",
]
__getattr__ = _make_getattr(__name__)
+3 -2
View File
@@ -23,6 +23,7 @@ from attr import __version_info__ as __version_info__
from attr import _FilterType
from attr import assoc as assoc
from attr import Attribute as Attribute
from attr import AttrsInstance as AttrsInstance
from attr import cmp_using as cmp_using
from attr import converters as converters
from attr import define as define
@@ -45,7 +46,7 @@ from attr import validators as validators
# TODO: see definition of attr.asdict/astuple
def asdict(
inst: Any,
inst: AttrsInstance,
recurse: bool = ...,
filter: Optional[_FilterType[Any]] = ...,
dict_factory: Type[Mapping[Any, Any]] = ...,
@@ -58,7 +59,7 @@ def asdict(
# TODO: add support for returning NamedTuple from the mypy plugin
def astuple(
inst: Any,
inst: AttrsInstance,
recurse: bool = ...,
filter: Optional[_FilterType[Any]] = ...,
tuple_factory: Type[Sequence[Any]] = ...,
+1 -3
View File
@@ -41,7 +41,6 @@ from .termui import clear as clear
from .termui import confirm as confirm
from .termui import echo_via_pager as echo_via_pager
from .termui import edit as edit
from .termui import get_terminal_size as get_terminal_size
from .termui import getchar as getchar
from .termui import launch as launch
from .termui import pause as pause
@@ -68,8 +67,7 @@ from .utils import echo as echo
from .utils import format_filename as format_filename
from .utils import get_app_dir as get_app_dir
from .utils import get_binary_stream as get_binary_stream
from .utils import get_os_args as get_os_args
from .utils import get_text_stream as get_text_stream
from .utils import open_file as open_file
__version__ = "8.0.3"
__version__ = "8.1.3"
+3 -4
View File
@@ -388,9 +388,9 @@ def open_stream(
) -> t.Tuple[t.IO, bool]:
binary = "b" in mode
# Standard streams first. These are simple because they don't need
# special handling for the atomic flag. It's entirely ignored.
if filename == "-":
# Standard streams first. These are simple because they ignore the
# atomic flag. Use fsdecode to handle Path("-").
if os.fsdecode(filename) == "-":
if any(m in mode for m in ["w", "a", "x"]):
if binary:
return get_binary_stdout(), False
@@ -561,7 +561,6 @@ if sys.platform.startswith("win") and WIN:
return rv
else:
def _get_argv_encoding() -> str:
-1
View File
@@ -675,7 +675,6 @@ if WIN:
_translate_ch_to_exc(rv)
return rv
else:
import tty
import termios
-100
View File
@@ -1,100 +0,0 @@
import codecs
import os
from gettext import gettext as _
def _verify_python_env() -> None:
"""Ensures that the environment is good for Unicode."""
try:
from locale import getpreferredencoding
fs_enc = codecs.lookup(getpreferredencoding()).name
except Exception:
fs_enc = "ascii"
if fs_enc != "ascii":
return
extra = [
_(
"Click will abort further execution because Python was"
" configured to use ASCII as encoding for the environment."
" Consult https://click.palletsprojects.com/unicode-support/"
" for mitigation steps."
)
]
if os.name == "posix":
import subprocess
try:
rv = subprocess.Popen(
["locale", "-a"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="ascii",
errors="replace",
).communicate()[0]
except OSError:
rv = ""
good_locales = set()
has_c_utf8 = False
for line in rv.splitlines():
locale = line.strip()
if locale.lower().endswith((".utf-8", ".utf8")):
good_locales.add(locale)
if locale.lower() in ("c.utf8", "c.utf-8"):
has_c_utf8 = True
if not good_locales:
extra.append(
_(
"Additional information: on this system no suitable"
" UTF-8 locales were discovered. This most likely"
" requires resolving by reconfiguring the locale"
" system."
)
)
elif has_c_utf8:
extra.append(
_(
"This system supports the C.UTF-8 locale which is"
" recommended. You might be able to resolve your"
" issue by exporting the following environment"
" variables:"
)
)
extra.append(" export LC_ALL=C.UTF-8\n export LANG=C.UTF-8")
else:
extra.append(
_(
"This system lists some UTF-8 supporting locales"
" that you can pick from. The following suitable"
" locales were discovered: {locales}"
).format(locales=", ".join(sorted(good_locales)))
)
bad_locale = None
for env_locale in os.environ.get("LC_ALL"), os.environ.get("LANG"):
if env_locale and env_locale.lower().endswith((".utf-8", ".utf8")):
bad_locale = env_locale
if env_locale is not None:
break
if bad_locale is not None:
extra.append(
_(
"Click discovered that you exported a UTF-8 locale"
" but the locale system could not pick up from it"
" because it does not exist. The exported locale is"
" {locale!r} but it is not supported."
).format(locale=bad_locale)
)
raise RuntimeError("\n\n".join(extra))
+157 -112
View File
@@ -1,8 +1,8 @@
import enum
import errno
import inspect
import os
import sys
import typing
import typing as t
from collections import abc
from contextlib import contextmanager
@@ -14,7 +14,6 @@ from gettext import ngettext
from itertools import repeat
from . import types
from ._unicodefun import _verify_python_env
from .exceptions import Abort
from .exceptions import BadParameter
from .exceptions import ClickException
@@ -224,9 +223,14 @@ class Context:
codes are used in texts that Click prints which is by
default not the case. This for instance would affect
help output.
:param show_default: Show defaults for all options. If not set,
defaults to the value from a parent context. Overrides an
option's ``show_default`` argument.
:param show_default: Show the default value for commands. If this
value is not set, it defaults to the value from the parent
context. ``Command.show_default`` overrides this default for the
specific command.
.. versionchanged:: 8.1
The ``show_default`` parameter is overridden by
``Command.show_default``, instead of the other way around.
.. versionchanged:: 8.0
The ``show_default`` parameter defaults to the value from the
@@ -288,6 +292,8 @@ class Context:
#: must be never propagated to another arguments. This is used
#: to implement nested parsing.
self.protected_args: t.List[str] = []
#: the collected prefixes of the command's options.
self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set()
if obj is None and parent is not None:
obj = parent.obj
@@ -632,13 +638,13 @@ class Context:
self.obj = rv = object_type()
return rv
@typing.overload
@t.overload
def lookup_default(
self, name: str, call: "te.Literal[True]" = True
) -> t.Optional[t.Any]:
...
@typing.overload
@t.overload
def lookup_default(
self, name: str, call: "te.Literal[False]" = ...
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
@@ -956,7 +962,7 @@ class BaseCommand:
return results
@typing.overload
@t.overload
def main(
self,
args: t.Optional[t.Sequence[str]] = None,
@@ -967,7 +973,7 @@ class BaseCommand:
) -> "te.NoReturn":
...
@typing.overload
@t.overload
def main(
self,
args: t.Optional[t.Sequence[str]] = None,
@@ -1029,10 +1035,6 @@ class BaseCommand:
.. versionchanged:: 3.0
Added the ``standalone_mode`` parameter.
"""
# Verify that the environment is configured correctly, or reject
# further execution to avoid a broken script.
_verify_python_env()
if args is None:
args = sys.argv[1:]
@@ -1133,13 +1135,6 @@ class Command(BaseCommand):
Click. A basic command handles command line parsing and might dispatch
more parsing to commands nested below it.
.. versionchanged:: 2.0
Added the `context_settings` parameter.
.. versionchanged:: 8.0
Added repr showing the command name
.. versionchanged:: 7.1
Added the `no_args_is_help` parameter.
:param name: the name of the command to use unless a group overrides it.
:param context_settings: an optional dictionary with defaults that are
passed to the context object.
@@ -1161,6 +1156,20 @@ class Command(BaseCommand):
:param deprecated: issues a message indicating that
the command is deprecated.
.. versionchanged:: 8.1
``help``, ``epilog``, and ``short_help`` are stored unprocessed,
all formatting is done when outputting help text, not at init,
and is done even if not using the ``@command`` decorator.
.. versionchanged:: 8.0
Added a ``repr`` showing the command name.
.. versionchanged:: 7.1
Added the ``no_args_is_help`` parameter.
.. versionchanged:: 2.0
Added the ``context_settings`` parameter.
"""
def __init__(
@@ -1186,12 +1195,6 @@ class Command(BaseCommand):
#: should show up in the help page and execute. Eager parameters
#: will automatically be handled before non eager ones.
self.params: t.List["Parameter"] = params or []
# if a form feed (page break) is found in the help text, truncate help
# text to the content preceding the first form feed
if help and "\f" in help:
help = help.split("\f", 1)[0]
self.help = help
self.epilog = epilog
self.options_metavar = options_metavar
@@ -1299,10 +1302,12 @@ class Command(BaseCommand):
"""Gets short help for the command or makes it by shortening the
long help string.
"""
text = self.short_help or ""
if not text and self.help:
if self.short_help:
text = inspect.cleandoc(self.short_help)
elif self.help:
text = make_default_short_help(self.help, limit)
else:
text = ""
if self.deprecated:
text = _("(Deprecated) {text}").format(text=text)
@@ -1328,12 +1333,13 @@ class Command(BaseCommand):
def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None:
"""Writes the help text to the formatter if it exists."""
text = self.help or ""
text = self.help if self.help is not None else ""
if self.deprecated:
text = _("(Deprecated) {text}").format(text=text)
if text:
text = inspect.cleandoc(text).partition("\f")[0]
formatter.write_paragraph()
with formatter.indentation():
@@ -1354,9 +1360,11 @@ class Command(BaseCommand):
def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None:
"""Writes the epilog into the formatter if it exists."""
if self.epilog:
epilog = inspect.cleandoc(self.epilog)
formatter.write_paragraph()
with formatter.indentation():
formatter.write_text(self.epilog)
formatter.write_text(epilog)
def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]:
if not args and self.no_args_is_help and not ctx.resilient_parsing:
@@ -1379,6 +1387,7 @@ class Command(BaseCommand):
)
ctx.args = args
ctx._opt_prefixes.update(parser._opt_prefixes)
return args
def invoke(self, ctx: Context) -> t.Any:
@@ -1568,17 +1577,6 @@ class MultiCommand(Command):
return decorator
def resultcallback(self, replace: bool = False) -> t.Callable[[F], F]:
import warnings
warnings.warn(
"'resultcallback' has been renamed to 'result_callback'."
" The old name will be removed in Click 8.1.",
DeprecationWarning,
stacklevel=2,
)
return self.result_callback(replace=replace)
def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None:
"""Extra format methods for multi methods that adds all the commands
after the options.
@@ -1631,11 +1629,11 @@ class MultiCommand(Command):
if not ctx.protected_args:
if self.invoke_without_command:
# No subcommand was invoked, so the result callback is
# invoked with None for regular groups, or an empty list
# for chained groups.
# invoked with the group return value for regular
# groups, or an empty list for chained groups.
with ctx:
super().invoke(ctx)
return _process_result([] if self.chain else None)
rv = super().invoke(ctx)
return _process_result([] if self.chain else rv)
ctx.fail(_("Missing command."))
# Fetch args back out
@@ -1811,9 +1809,19 @@ class Group(MultiCommand):
_check_multicommand(self, name, cmd, register=True)
self.commands[name] = cmd
@t.overload
def command(self, __func: t.Callable[..., t.Any]) -> Command:
...
@t.overload
def command(
self, *args: t.Any, **kwargs: t.Any
) -> t.Callable[[t.Callable[..., t.Any]], Command]:
...
def command(
self, *args: t.Any, **kwargs: t.Any
) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]:
"""A shortcut decorator for declaring and attaching a command to
the group. This takes the same arguments as :func:`command` and
immediately registers the created command with this group by
@@ -1822,24 +1830,49 @@ class Group(MultiCommand):
To customize the command class used, set the
:attr:`command_class` attribute.
.. versionchanged:: 8.1
This decorator can be applied without parentheses.
.. versionchanged:: 8.0
Added the :attr:`command_class` attribute.
"""
from .decorators import command
if self.command_class is not None and "cls" not in kwargs:
if self.command_class and kwargs.get("cls") is None:
kwargs["cls"] = self.command_class
func: t.Optional[t.Callable] = None
if args and callable(args[0]):
assert (
len(args) == 1 and not kwargs
), "Use 'command(**kwargs)(callable)' to provide arguments."
(func,) = args
args = ()
def decorator(f: t.Callable[..., t.Any]) -> Command:
cmd = command(*args, **kwargs)(f)
cmd: Command = command(*args, **kwargs)(f)
self.add_command(cmd)
return cmd
if func is not None:
return decorator(func)
return decorator
@t.overload
def group(self, __func: t.Callable[..., t.Any]) -> "Group":
...
@t.overload
def group(
self, *args: t.Any, **kwargs: t.Any
) -> t.Callable[[t.Callable[..., t.Any]], "Group"]:
...
def group(
self, *args: t.Any, **kwargs: t.Any
) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]:
"""A shortcut decorator for declaring and attaching a group to
the group. This takes the same arguments as :func:`group` and
immediately registers the created group with this group by
@@ -1848,22 +1881,37 @@ class Group(MultiCommand):
To customize the group class used, set the :attr:`group_class`
attribute.
.. versionchanged:: 8.1
This decorator can be applied without parentheses.
.. versionchanged:: 8.0
Added the :attr:`group_class` attribute.
"""
from .decorators import group
if self.group_class is not None and "cls" not in kwargs:
func: t.Optional[t.Callable] = None
if args and callable(args[0]):
assert (
len(args) == 1 and not kwargs
), "Use 'group(**kwargs)(callable)' to provide arguments."
(func,) = args
args = ()
if self.group_class is not None and kwargs.get("cls") is None:
if self.group_class is type:
kwargs["cls"] = type(self)
else:
kwargs["cls"] = self.group_class
def decorator(f: t.Callable[..., t.Any]) -> "Group":
cmd = group(*args, **kwargs)(f)
cmd: Group = group(*args, **kwargs)(f)
self.add_command(cmd)
return cmd
if func is not None:
return decorator(func)
return decorator
def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
@@ -2020,11 +2068,6 @@ class Parameter:
t.Union[t.List["CompletionItem"], t.List[str]],
]
] = None,
autocompletion: t.Optional[
t.Callable[
[Context, t.List[str], str], t.List[t.Union[t.Tuple[str, str], str]]
]
] = None,
) -> None:
self.name, self.opts, self.secondary_opts = self._parse_decls(
param_decls or (), expose_value
@@ -2048,36 +2091,6 @@ class Parameter:
self.is_eager = is_eager
self.metavar = metavar
self.envvar = envvar
if autocompletion is not None:
import warnings
warnings.warn(
"'autocompletion' is renamed to 'shell_complete'. The old name is"
" deprecated and will be removed in Click 8.1. See the docs about"
" 'Parameter' for information about new behavior.",
DeprecationWarning,
stacklevel=2,
)
def shell_complete(
ctx: Context, param: "Parameter", incomplete: str
) -> t.List["CompletionItem"]:
from pipenv.vendor.click.shell_completion import CompletionItem
out = []
for c in autocompletion(ctx, [], incomplete): # type: ignore
if isinstance(c, tuple):
c = CompletionItem(c[0], help=c[1])
elif isinstance(c, str):
c = CompletionItem(c)
if c.value.startswith(incomplete):
out.append(c)
return out
self._custom_shell_complete = shell_complete
if __debug__:
@@ -2172,13 +2185,13 @@ class Parameter:
return metavar
@typing.overload
@t.overload
def get_default(
self, ctx: Context, call: "te.Literal[True]" = True
) -> t.Optional[t.Any]:
...
@typing.overload
@t.overload
def get_default(
self, ctx: Context, call: bool = ...
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
@@ -2399,25 +2412,27 @@ class Option(Parameter):
All other parameters are passed onwards to the parameter constructor.
:param show_default: controls if the default value should be shown on the
help page. Normally, defaults are not shown. If this
value is a string, it shows the string instead of the
value. This is particularly useful for dynamic options.
:param show_envvar: controls if an environment variable should be shown on
the help page. Normally, environment variables
are not shown.
:param prompt: if set to `True` or a non empty string then the user will be
prompted for input. If set to `True` the prompt will be the
option name capitalized.
:param show_default: Show the default value for this option in its
help text. Values are not shown by default, unless
:attr:`Context.show_default` is ``True``. If this value is a
string, it shows that string in parentheses instead of the
actual value. This is particularly useful for dynamic options.
For single option boolean flags, the default remains hidden if
its value is ``False``.
:param show_envvar: Controls if an environment variable should be
shown on the help page. Normally, environment variables are not
shown.
:param prompt: If set to ``True`` or a non empty string then the
user will be prompted for input. If set to ``True`` the prompt
will be the option name capitalized.
:param confirmation_prompt: Prompt a second time to confirm the
value if it was prompted for. Can be set to a string instead of
``True`` to customize the message.
:param prompt_required: If set to ``False``, the user will be
prompted for input only when the option was specified as a flag
without a value.
:param hide_input: if this is `True` then the input on the prompt will be
hidden from the user. This is useful for password
input.
:param hide_input: If this is ``True`` then the input on the prompt
will be hidden from the user. This is useful for password input.
:param is_flag: forces this option to act as a flag. The default is
auto detection.
:param flag_value: which value should be used for this flag if it's
@@ -2435,6 +2450,18 @@ class Option(Parameter):
:param help: the help string.
:param hidden: hide this option from help outputs.
.. versionchanged:: 8.1.0
Help text indentation is cleaned here instead of only in the
``@option`` decorator.
.. versionchanged:: 8.1.0
The ``show_default`` parameter overrides
``Context.show_default``.
.. versionchanged:: 8.1.0
The default of a single option boolean flag is not shown if the
default value is ``False``.
.. versionchanged:: 8.0.1
``type`` is detected from ``flag_value`` if given.
"""
@@ -2444,7 +2471,7 @@ class Option(Parameter):
def __init__(
self,
param_decls: t.Optional[t.Sequence[str]] = None,
show_default: t.Union[bool, str] = False,
show_default: t.Union[bool, str, None] = None,
prompt: t.Union[bool, str] = False,
confirmation_prompt: t.Union[bool, str] = False,
prompt_required: bool = True,
@@ -2461,6 +2488,9 @@ class Option(Parameter):
show_envvar: bool = False,
**attrs: t.Any,
) -> None:
if help:
help = inspect.cleandoc(help)
default_is_missing = "default" not in attrs
super().__init__(param_decls, type=type, multiple=multiple, **attrs)
@@ -2472,7 +2502,7 @@ class Option(Parameter):
elif prompt is False:
prompt_text = None
else:
prompt_text = t.cast(str, prompt)
prompt_text = prompt
self.prompt = prompt_text
self.confirmation_prompt = confirmation_prompt
@@ -2499,7 +2529,7 @@ class Option(Parameter):
# flag if flag_value is set.
self._flag_needs_value = flag_value is not None
if is_flag and default_is_missing:
if is_flag and default_is_missing and not self.required:
self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False
if flag_value is None:
@@ -2550,6 +2580,9 @@ class Option(Parameter):
if self.is_flag:
raise TypeError("'count' is not valid with 'is_flag'.")
if self.multiple and self.is_flag:
raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.")
def to_info_dict(self) -> t.Dict[str, t.Any]:
info_dict = super().to_info_dict()
info_dict.update(
@@ -2711,16 +2744,23 @@ class Option(Parameter):
finally:
ctx.resilient_parsing = resilient
show_default_is_str = isinstance(self.show_default, str)
show_default = False
show_default_is_str = False
if show_default_is_str or (
default_value is not None and (self.show_default or ctx.show_default)
):
if self.show_default is not None:
if isinstance(self.show_default, str):
show_default_is_str = show_default = True
else:
show_default = self.show_default
elif ctx.show_default is not None:
show_default = ctx.show_default
if show_default_is_str or (show_default and (default_value is not None)):
if show_default_is_str:
default_string = f"({self.show_default})"
elif isinstance(default_value, (list, tuple)):
default_string = ", ".join(str(d) for d in default_value)
elif callable(default_value):
elif inspect.isfunction(default_value):
default_string = _("(dynamic)")
elif self.is_bool_flag and self.secondary_opts:
# For boolean flags that have distinct True/False opts,
@@ -2728,6 +2768,8 @@ class Option(Parameter):
default_string = split_opt(
(self.opts if self.default else self.secondary_opts)[0]
)[1]
elif self.is_bool_flag and not self.secondary_opts and not default_value:
default_string = ""
else:
default_string = str(default_value)
@@ -2753,13 +2795,13 @@ class Option(Parameter):
return ("; " if any_prefix_is_slash else " / ").join(rv), help
@typing.overload
@t.overload
def get_default(
self, ctx: Context, call: "te.Literal[True]" = True
) -> t.Optional[t.Any]:
...
@typing.overload
@t.overload
def get_default(
self, ctx: Context, call: bool = ...
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
@@ -2770,7 +2812,7 @@ class Option(Parameter):
) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
# If we're a non boolean flag our default is more complex because
# we need to look at all flags in the same group to figure out
# if we're the the default one in which case we return the flag
# if we're the default one in which case we return the flag
# value as default.
if self.is_flag and not self.is_bool_flag:
for param in ctx.command.params:
@@ -2821,7 +2863,10 @@ class Option(Parameter):
envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
rv = os.environ.get(envvar)
return rv
if rv:
return rv
return None
def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]:
rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx)
+97 -36
View File
@@ -14,7 +14,7 @@ from .globals import get_current_context
from .utils import echo
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
FC = t.TypeVar("FC", t.Callable[..., t.Any], Command)
FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command])
def pass_context(f: F) -> F:
@@ -121,43 +121,38 @@ def pass_meta_key(
return decorator
def _make_command(
f: F,
name: t.Optional[str],
attrs: t.MutableMapping[str, t.Any],
cls: t.Type[Command],
CmdType = t.TypeVar("CmdType", bound=Command)
@t.overload
def command(
__func: t.Callable[..., t.Any],
) -> Command:
if isinstance(f, Command):
raise TypeError("Attempted to convert a callback into a command twice.")
...
try:
params = f.__click_params__ # type: ignore
params.reverse()
del f.__click_params__ # type: ignore
except AttributeError:
params = []
help = attrs.get("help")
@t.overload
def command(
name: t.Optional[str] = None,
**attrs: t.Any,
) -> t.Callable[..., Command]:
...
if help is None:
help = inspect.getdoc(f)
else:
help = inspect.cleandoc(help)
attrs["help"] = help
return cls(
name=name or f.__name__.lower().replace("_", "-"),
callback=f,
params=params,
**attrs,
)
@t.overload
def command(
name: t.Optional[str] = None,
cls: t.Type[CmdType] = ...,
**attrs: t.Any,
) -> t.Callable[..., CmdType]:
...
def command(
name: t.Optional[str] = None,
name: t.Union[str, t.Callable[..., t.Any], None] = None,
cls: t.Optional[t.Type[Command]] = None,
**attrs: t.Any,
) -> t.Callable[[F], Command]:
) -> t.Union[Command, t.Callable[..., Command]]:
r"""Creates a new :class:`Command` and uses the decorated function as
callback. This will also automatically attach all decorated
:func:`option`\s and :func:`argument`\s as parameters to the command.
@@ -167,6 +162,8 @@ def command(
pass the intended name as the first argument.
All keyword arguments are forwarded to the underlying command class.
For the ``params`` argument, any decorated params are appended to
the end of the list.
Once decorated the function turns into a :class:`Command` instance
that can be invoked as a command line utility or be attached to a
@@ -176,24 +173,91 @@ def command(
name with underscores replaced by dashes.
:param cls: the command class to instantiate. This defaults to
:class:`Command`.
.. versionchanged:: 8.1
This decorator can be applied without parentheses.
.. versionchanged:: 8.1
The ``params`` argument can be used. Decorated params are
appended to the end of the list.
"""
func: t.Optional[t.Callable[..., t.Any]] = None
if callable(name):
func = name
name = None
assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class."
assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments."
if cls is None:
cls = Command
def decorator(f: t.Callable[..., t.Any]) -> Command:
cmd = _make_command(f, name, attrs, cls) # type: ignore
if isinstance(f, Command):
raise TypeError("Attempted to convert a callback into a command twice.")
attr_params = attrs.pop("params", None)
params = attr_params if attr_params is not None else []
try:
decorator_params = f.__click_params__ # type: ignore
except AttributeError:
pass
else:
del f.__click_params__ # type: ignore
params.extend(reversed(decorator_params))
if attrs.get("help") is None:
attrs["help"] = f.__doc__
cmd = cls( # type: ignore[misc]
name=name or f.__name__.lower().replace("_", "-"), # type: ignore[arg-type]
callback=f,
params=params,
**attrs,
)
cmd.__doc__ = f.__doc__
return cmd
if func is not None:
return decorator(func)
return decorator
def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]:
@t.overload
def group(
__func: t.Callable[..., t.Any],
) -> Group:
...
@t.overload
def group(
name: t.Optional[str] = None,
**attrs: t.Any,
) -> t.Callable[[F], Group]:
...
def group(
name: t.Union[str, t.Callable[..., t.Any], None] = None, **attrs: t.Any
) -> t.Union[Group, t.Callable[[F], Group]]:
"""Creates a new :class:`Group` with a function as callback. This
works otherwise the same as :func:`command` just that the `cls`
parameter is set to :class:`Group`.
.. versionchanged:: 8.1
This decorator can be applied without parentheses.
"""
attrs.setdefault("cls", Group)
if attrs.get("cls") is None:
attrs["cls"] = Group
if callable(name):
grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs))
return grp(name)
return t.cast(Group, command(name, **attrs))
@@ -219,7 +283,7 @@ def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
"""
def decorator(f: FC) -> FC:
ArgumentClass = attrs.pop("cls", Argument)
ArgumentClass = attrs.pop("cls", None) or Argument
_param_memo(f, ArgumentClass(param_decls, **attrs))
return f
@@ -240,10 +304,7 @@ def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
def decorator(f: FC) -> FC:
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
option_attrs = attrs.copy()
if "help" in option_attrs:
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
OptionClass = option_attrs.pop("cls", Option)
OptionClass = option_attrs.pop("cls", None) or Option
_param_memo(f, OptionClass(param_decls, **option_attrs))
return f
+2 -3
View File
@@ -1,4 +1,3 @@
import typing
import typing as t
from threading import local
@@ -9,12 +8,12 @@ if t.TYPE_CHECKING:
_local = local()
@typing.overload
@t.overload
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
...
@typing.overload
@t.overload
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
...
+10 -11
View File
@@ -102,10 +102,10 @@ _SOURCE_BASH = """\
IFS=',' read type value <<< "$completion"
if [[ $type == 'dir' ]]; then
COMREPLY=()
COMPREPLY=()
compopt -o dirnames
elif [[ $type == 'file' ]]; then
COMREPLY=()
COMPREPLY=()
compopt -o default
elif [[ $type == 'plain' ]]; then
COMPREPLY+=($value)
@@ -448,17 +448,16 @@ def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
)
def _start_of_option(value: str) -> bool:
def _start_of_option(ctx: Context, value: str) -> bool:
"""Check if the value looks like the start of an option."""
if not value:
return False
c = value[0]
# Allow "/" since that starts a path.
return not c.isalnum() and c != "/"
return c in ctx._opt_prefixes
def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool:
"""Determine if the given parameter is an option that needs a value.
:param args: List of complete args before the incomplete value.
@@ -467,7 +466,7 @@ def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
if not isinstance(param, Option):
return False
if param.is_flag:
if param.is_flag or param.count:
return False
last_option = None
@@ -476,7 +475,7 @@ def _is_incomplete_option(args: t.List[str], param: Parameter) -> bool:
if index + 1 > param.nargs:
break
if _start_of_option(arg):
if _start_of_option(ctx, arg):
last_option = arg
return last_option is not None and last_option in param.opts
@@ -551,7 +550,7 @@ def _resolve_incomplete(
# split and discard the "=" to make completion easier.
if incomplete == "=":
incomplete = ""
elif "=" in incomplete and _start_of_option(incomplete):
elif "=" in incomplete and _start_of_option(ctx, incomplete):
name, _, incomplete = incomplete.partition("=")
args.append(name)
@@ -559,7 +558,7 @@ def _resolve_incomplete(
# even if they start with the option character. If it hasn't been
# given and the incomplete arg looks like an option, the current
# command will provide option name completions.
if "--" not in args and _start_of_option(incomplete):
if "--" not in args and _start_of_option(ctx, incomplete):
return ctx.command, incomplete
params = ctx.command.get_params(ctx)
@@ -567,7 +566,7 @@ def _resolve_incomplete(
# If the last complete arg is an option name with an incomplete
# value, the option will provide value completions.
for param in params:
if _is_incomplete_option(args, param):
if _is_incomplete_option(ctx, args, param):
return param, incomplete
# It's not an option name or value. The first argument without a
+4 -26
View File
@@ -3,7 +3,6 @@ import io
import itertools
import os
import sys
import typing
import typing as t
from gettext import gettext as _
@@ -94,7 +93,7 @@ def prompt(
"""Prompts a user for input. This is a convenience function that can
be used to prompt a user for input later.
If the user aborts the input by sending a interrupt signal, this
If the user aborts the input by sending an interrupt signal, this
function will catch it and raise a :exc:`Abort` exception.
:param text: the text to show for the prompt.
@@ -160,7 +159,6 @@ def prompt(
if confirmation_prompt is True:
confirmation_prompt = _("Repeat for confirmation")
confirmation_prompt = t.cast(str, confirmation_prompt)
confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
while True:
@@ -182,9 +180,9 @@ def prompt(
if not confirmation_prompt:
return result
while True:
confirmation_prompt = t.cast(str, confirmation_prompt)
value2 = prompt_func(confirmation_prompt)
if value2:
is_empty = not value and not value2
if value2 or is_empty:
break
if value == value2:
return result
@@ -252,26 +250,6 @@ def confirm(
return rv
def get_terminal_size() -> os.terminal_size:
"""Returns the current size of the terminal as tuple in the form
``(width, height)`` in columns and rows.
.. deprecated:: 8.0
Will be removed in Click 8.1. Use
:func:`shutil.get_terminal_size` instead.
"""
import shutil
import warnings
warnings.warn(
"'click.get_terminal_size()' is deprecated and will be removed"
" in Click 8.1. Use 'shutil.get_terminal_size()' instead.",
DeprecationWarning,
stacklevel=2,
)
return shutil.get_terminal_size()
def echo_via_pager(
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
color: t.Optional[bool] = None,
@@ -627,7 +605,7 @@ def unstyle(text: str) -> str:
def secho(
message: t.Optional[t.Any] = None,
file: t.Optional[t.IO] = None,
file: t.Optional[t.IO[t.AnyStr]] = None,
nl: bool = True,
err: bool = False,
color: t.Optional[bool] = None,
+4 -4
View File
@@ -464,16 +464,16 @@ class CliRunner:
Added the ``temp_dir`` parameter.
"""
cwd = os.getcwd()
t = tempfile.mkdtemp(dir=temp_dir)
os.chdir(t)
dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var]
os.chdir(dt)
try:
yield t
yield t.cast(str, dt)
finally:
os.chdir(cwd)
if temp_dir is None:
try:
shutil.rmtree(t)
shutil.rmtree(dt)
except OSError: # noqa: B014
pass
+45 -24
View File
@@ -63,7 +63,14 @@ class ParamType:
# The class name without the "ParamType" suffix.
param_type = type(self).__name__.partition("ParamType")[0]
param_type = param_type.partition("ParameterType")[0]
return {"param_type": param_type, "name": self.name}
# Custom subclasses might not remember to set a name.
if hasattr(self, "name"):
name = self.name
else:
name = param_type
return {"param_type": param_type, "name": name}
def __call__(
self,
@@ -724,7 +731,7 @@ class File(ParamType):
return f
except OSError as e: # noqa: B014
self.fail(f"{os.fsdecode(value)!r}: {e.strerror}", param, ctx)
self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx)
def shell_complete(
self, ctx: "Context", param: "Parameter", incomplete: str
@@ -744,30 +751,31 @@ class File(ParamType):
class Path(ParamType):
"""The path type is similar to the :class:`File` type but it performs
different checks. First of all, instead of returning an open file
handle it returns just the filename. Secondly, it can perform various
basic checks about what the file or directory should be.
"""The ``Path`` type is similar to the :class:`File` type, but
returns the filename instead of an open file. Various checks can be
enabled to validate the type of file and permissions.
:param exists: if set to true, the file or directory needs to exist for
this value to be valid. If this is not required and a
file does indeed not exist, then all further checks are
silently skipped.
:param file_okay: controls if a file is a possible value.
:param dir_okay: controls if a directory is a possible value.
:param writable: if true, a writable check is performed.
:param exists: The file or directory needs to exist for the value to
be valid. If this is not set to ``True``, and the file does not
exist, then all further checks are silently skipped.
:param file_okay: Allow a file as a value.
:param dir_okay: Allow a directory as a value.
:param readable: if true, a readable check is performed.
:param resolve_path: if this is true, then the path is fully resolved
before the value is passed onwards. This means
that it's absolute and symlinks are resolved. It
will not expand a tilde-prefix, as this is
supposed to be done by the shell only.
:param allow_dash: If this is set to `True`, a single dash to indicate
standard streams is permitted.
:param writable: if true, a writable check is performed.
:param executable: if true, an executable check is performed.
:param resolve_path: Make the value absolute and resolve any
symlinks. A ``~`` is not expanded, as this is supposed to be
done by the shell only.
:param allow_dash: Allow a single dash as a value, which indicates
a standard stream (but does not open it). Use
:func:`~click.open_file` to handle opening this value.
:param path_type: Convert the incoming path value to this type. If
``None``, keep Python's default, which is ``str``. Useful to
convert to :class:`pathlib.Path`.
.. versionchanged:: 8.1
Added the ``executable`` parameter.
.. versionchanged:: 8.0
Allow passing ``type=pathlib.Path``.
@@ -787,12 +795,14 @@ class Path(ParamType):
resolve_path: bool = False,
allow_dash: bool = False,
path_type: t.Optional[t.Type] = None,
executable: bool = False,
):
self.exists = exists
self.file_okay = file_okay
self.dir_okay = dir_okay
self.writable = writable
self.readable = readable
self.writable = writable
self.executable = executable
self.resolve_path = resolve_path
self.allow_dash = allow_dash
self.type = path_type
@@ -865,12 +875,22 @@ class Path(ParamType):
)
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
self.fail(
_("{name} {filename!r} is a directory.").format(
_("{name} '{filename}' is a directory.").format(
name=self.name.title(), filename=os.fsdecode(value)
),
param,
ctx,
)
if self.readable and not os.access(rv, os.R_OK):
self.fail(
_("{name} {filename!r} is not readable.").format(
name=self.name.title(), filename=os.fsdecode(value)
),
param,
ctx,
)
if self.writable and not os.access(rv, os.W_OK):
self.fail(
_("{name} {filename!r} is not writable.").format(
@@ -879,9 +899,10 @@ class Path(ParamType):
param,
ctx,
)
if self.readable and not os.access(rv, os.R_OK):
if self.executable and not os.access(value, os.X_OK):
self.fail(
_("{name} {filename!r} is not readable.").format(
_("{name} {filename!r} is not executable.").format(
name=self.name.title(), filename=os.fsdecode(value)
),
param,
+39 -38
View File
@@ -1,4 +1,5 @@
import os
import re
import sys
import typing as t
from functools import update_wrapper
@@ -203,7 +204,7 @@ class KeepOpenFile:
def echo(
message: t.Optional[t.Any] = None,
file: t.Optional[t.IO] = None,
file: t.Optional[t.IO[t.Any]] = None,
nl: bool = True,
err: bool = False,
color: t.Optional[bool] = None,
@@ -340,55 +341,45 @@ def open_file(
lazy: bool = False,
atomic: bool = False,
) -> t.IO:
"""This is similar to how the :class:`File` works but for manual
usage. Files are opened non lazy by default. This can open regular
files as well as stdin/stdout if ``'-'`` is passed.
"""Open a file, with extra behavior to handle ``'-'`` to indicate
a standard stream, lazy open on write, and atomic write. Similar to
the behavior of the :class:`~click.File` param type.
If stdin/stdout is returned the stream is wrapped so that the context
manager will not close the stream accidentally. This makes it possible
to always use the function like this without having to worry to
accidentally close a standard stream::
If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
wrapped so that using it in a context manager will not close it.
This makes it possible to use the function without accidentally
closing a standard stream:
.. code-block:: python
with open_file(filename) as f:
...
.. versionadded:: 3.0
:param filename: The name of the file to open, or ``'-'`` for
``stdin``/``stdout``.
:param mode: The mode in which to open the file.
:param encoding: The encoding to decode or encode a file opened in
text mode.
:param errors: The error handling mode.
:param lazy: Wait to open the file until it is accessed. For read
mode, the file is temporarily opened to raise access errors
early, then closed until it is read again.
:param atomic: Write to a temporary file and replace the given file
on close.
:param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
:param mode: the mode in which to open the file.
:param encoding: the encoding to use.
:param errors: the error handling for this file.
:param lazy: can be flipped to true to open the file lazily.
:param atomic: in atomic mode writes go into a temporary file and it's
moved on close.
.. versionadded:: 3.0
"""
if lazy:
return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic))
f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
if not should_close:
f = t.cast(t.IO, KeepOpenFile(f))
return f
def get_os_args() -> t.Sequence[str]:
"""Returns the argument part of ``sys.argv``, removing the first
value which is the name of the script.
.. deprecated:: 8.0
Will be removed in Click 8.1. Access ``sys.argv[1:]`` directly
instead.
"""
import warnings
warnings.warn(
"'get_os_args' is deprecated and will be removed in Click 8.1."
" Access 'sys.argv[1:]' directly instead.",
DeprecationWarning,
stacklevel=2,
)
return sys.argv[1:]
def format_filename(
filename: t.Union[str, bytes, os.PathLike], shorten: bool = False
) -> str:
@@ -484,7 +475,7 @@ class PacifyFlushWrapper:
def _detect_program_name(
path: t.Optional[str] = None, _main: ModuleType = sys.modules["__main__"]
path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None
) -> str:
"""Determine the command used to run the program, for use in help
text. If a file or entry point was executed, the file name is
@@ -506,6 +497,9 @@ def _detect_program_name(
:meta private:
"""
if _main is None:
_main = sys.modules["__main__"]
if not path:
path = sys.argv[0]
@@ -546,7 +540,7 @@ def _expand_args(
See :func:`glob.glob`, :func:`os.path.expanduser`, and
:func:`os.path.expandvars`.
This intended for use on Windows, where the shell does not do any
This is intended for use on Windows, where the shell does not do any
expansion. It may not exactly match what a Unix shell would do.
:param args: List of command line arguments to expand.
@@ -554,6 +548,10 @@ def _expand_args(
:param env: Expand environment variables.
:param glob_recursive: ``**`` matches directories recursively.
.. versionchanged:: 8.1
Invalid glob patterns are treated as empty expansions rather
than raising an error.
.. versionadded:: 8.0
:meta private:
@@ -569,7 +567,10 @@ def _expand_args(
if env:
arg = os.path.expandvars(arg)
matches = glob(arg, recursive=glob_recursive)
try:
matches = glob(arg, recursive=glob_recursive)
except re.error:
matches = []
if not matches:
out.append(arg)
+28 -20
View File
@@ -1,48 +1,56 @@
# -*- coding: utf-8 -*-
"""
Extension for ``click`` to provide a group
with a git-like *did-you-mean* feature.
"""
"""
Extension for the python ``click`` module to provide
a group with a git-like *did-you-mean* feature.
"""
import difflib
import typing
import pipenv.vendor.click as click
import difflib
__version__ = "0.0.3"
class DYMMixin(object): # pylint: disable=too-few-public-methods
class DYMMixin:
"""
Mixin class for click MultiCommand inherited classes
to provide git-like *did-you-mean* functionality when
a certain command is not registered.
"""
def __init__(self, *args, **kwargs):
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
self.max_suggestions = kwargs.pop("max_suggestions", 3)
self.cutoff = kwargs.pop("cutoff", 0.5)
super(DYMMixin, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs) # type: ignore
def resolve_command(self, ctx, args):
def resolve_command(
self, ctx: click.Context, args: typing.List[str]
) -> typing.Tuple[
typing.Optional[str], typing.Optional[click.Command], typing.List[str]
]:
"""
Overrides clicks ``resolve_command`` method
and appends *Did you mean ...* suggestions
to the raised exception message.
"""
original_cmd_name = click.utils.make_str(args[0])
try:
return super(DYMMixin, self).resolve_command(ctx, args)
return super(DYMMixin, self).resolve_command(ctx, args) # type: ignore
except click.exceptions.UsageError as error:
error_msg = str(error)
matches = difflib.get_close_matches(original_cmd_name,
self.list_commands(ctx), self.max_suggestions, self.cutoff)
original_cmd_name = click.utils.make_str(args[0])
matches = difflib.get_close_matches(
original_cmd_name,
self.list_commands(ctx), # type: ignore
self.max_suggestions,
self.cutoff,
)
if matches:
error_msg += '\n\nDid you mean one of these?\n %s' % '\n '.join(matches) # pylint: disable=line-too-long
fmt_matches = "\n ".join(matches)
error_msg += "\n\n"
error_msg += f"Did you mean one of these?\n {fmt_matches}"
raise click.exceptions.UsageError(error_msg, error.ctx)
class DYMGroup(DYMMixin, click.Group): # pylint: disable=too-many-public-methods
class DYMGroup(DYMMixin, click.Group):
"""
click Group to provide git-like
*did-you-mean* functionality when a certain
@@ -50,7 +58,7 @@ class DYMGroup(DYMMixin, click.Group): # pylint: disable=too-many-public-method
"""
class DYMCommandCollection(DYMMixin, click.CommandCollection): # pylint: disable=too-many-public-methods
class DYMCommandCollection(DYMMixin, click.CommandCollection):
"""
click CommandCollection to provide git-like
*did-you-mean* functionality when a certain
+16 -9
View File
@@ -11,9 +11,10 @@ if t.TYPE_CHECKING:
pass
__version__ = "2.0.1"
__version__ = "2.1.2"
_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
_strip_comments_re = re.compile(r"<!--.*?-->", re.DOTALL)
_strip_tags_re = re.compile(r"<.*?>", re.DOTALL)
def _simple_escaping_wrapper(name: str) -> t.Callable[..., "Markup"]:
@@ -92,19 +93,24 @@ class Markup(str):
return NotImplemented
def __mul__(self, num: int) -> "Markup":
def __mul__(self, num: "te.SupportsIndex") -> "Markup":
if isinstance(num, int):
return self.__class__(super().__mul__(num))
return NotImplemented # type: ignore
return NotImplemented
__rmul__ = __mul__
def __mod__(self, arg: t.Any) -> "Markup":
if isinstance(arg, tuple):
# a tuple of arguments, each wrapped
arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
else:
elif hasattr(type(arg), "__getitem__") and not isinstance(arg, str):
# a mapping of arguments, wrapped
arg = _MarkupEscapeHelper(arg, self.escape)
else:
# a single argument, wrapped with the helper and a tuple
arg = (_MarkupEscapeHelper(arg, self.escape),)
return self.__class__(super().__mod__(arg))
@@ -153,8 +159,11 @@ class Markup(str):
>>> Markup("Main &raquo;\t<em>About</em>").striptags()
'Main » About'
"""
stripped = " ".join(_striptags_re.sub("", self).split())
return Markup(stripped).unescape()
# Use two regexes to avoid ambiguous matches.
value = _strip_comments_re.sub("", self)
value = _strip_tags_re.sub("", value)
value = " ".join(value.split())
return Markup(value).unescape()
@classmethod
def escape(cls, s: t.Any) -> "Markup":
@@ -280,9 +289,7 @@ try:
from ._speedups import escape as escape
from ._speedups import escape_silent as escape_silent
from ._speedups import soft_str as soft_str
from ._speedups import soft_unicode
except ImportError:
from ._native import escape as escape
from ._native import escape_silent as escape_silent # noqa: F401
from ._native import soft_str as soft_str # noqa: F401
from ._native import soft_unicode # noqa: F401
-12
View File
@@ -61,15 +61,3 @@ def soft_str(s: t.Any) -> str:
return str(s)
return s
def soft_unicode(s: t.Any) -> str:
import warnings
warnings.warn(
"'soft_unicode' has been renamed to 'soft_str'. The old name"
" will be removed in MarkupSafe 2.1.",
DeprecationWarning,
stacklevel=2,
)
return soft_str(s)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2015 Vineet Naik (naikvin@gmail.com)
Copyright (c) The pipdeptree developers
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
+125 -6
View File
@@ -1,4 +1,5 @@
import argparse
import fnmatch
import inspect
import json
import os
@@ -10,6 +11,7 @@ from collections import defaultdict, deque
from collections.abc import Mapping
from importlib import import_module
from itertools import chain
from textwrap import dedent
from pipenv.patched.pip._vendor import pkg_resources
@@ -349,14 +351,14 @@ class PackageDAG(Mapping):
m = {}
seen = set()
for node in self._obj.keys():
if node.key in exclude:
if any(fnmatch.fnmatch(node.key, e) for e in exclude):
continue
if include is None or node.key in include:
if include is None or any(fnmatch.fnmatch(node.key, i) for i in include):
stack.append(node)
while True:
if len(stack) > 0:
n = stack.pop()
cldn = [c for c in self._obj[n] if c.key not in exclude]
cldn = [c for c in self._obj[n] if not any(fnmatch.fnmatch(c.key, e) for e in exclude)]
m[n] = cldn
seen.add(n.key)
for c in cldn:
@@ -550,6 +552,103 @@ def render_json_tree(tree, indent):
return json.dumps([aux(p) for p in nodes], indent=indent)
def render_mermaid(tree) -> str:
"""Produce a Mermaid flowchart from the dependency graph.
:param dict tree: dependency graph
"""
# List of reserved keywords in Mermaid that cannot be used as node names.
# See: https://github.com/mermaid-js/mermaid/issues/4182#issuecomment-1454787806
reserved_ids: set[str] = {
"C4Component",
"C4Container",
"C4Deployment",
"C4Dynamic",
"_blank",
"_parent",
"_self",
"_top",
"call",
"class",
"classDef",
"click",
"end",
"flowchart",
"flowchart-v2",
"graph",
"interpolate",
"linkStyle",
"style",
"subgraph",
}
node_ids_map: dict[str:str] = {}
def mermaid_id(key: str) -> str:
"""Returns a valid Mermaid node ID from a string."""
# If we have already seen this key, return the canonical ID.
canonical_id = node_ids_map.get(key)
if canonical_id is not None:
return canonical_id
# If the key is not a reserved keyword, return it as is, and update the map.
if key not in reserved_ids:
node_ids_map[key] = key
return key
# If the key is a reserved keyword, append a number to it.
number = 0
while True:
new_id = f"{key}_{number}"
if new_id not in node_ids_map:
node_ids_map[key] = new_id
return new_id
number += 1
# Use a sets to avoid duplicate entries.
nodes: set[str] = set()
edges: set[str] = set()
if isinstance(tree, ReversedPackageDAG):
for package, reverse_dependencies in tree.items():
package_label = "\\n".join(
(package.project_name, "(missing)" if package.is_missing else package.installed_version)
)
package_key = mermaid_id(package.key)
nodes.add(f'{package_key}["{package_label}"]')
for reverse_dependency in reverse_dependencies:
edge_label = reverse_dependency.req.version_spec or "any"
reverse_dependency_key = mermaid_id(reverse_dependency.key)
edges.add(f'{package_key} -- "{edge_label}" --> {reverse_dependency_key}')
else:
for package, dependencies in tree.items():
package_label = "\\n".join((package.project_name, package.version))
package_key = mermaid_id(package.key)
nodes.add(f'{package_key}["{package_label}"]')
for dependency in dependencies:
edge_label = dependency.version_spec or "any"
dependency_key = mermaid_id(dependency.key)
if dependency.is_missing:
dependency_label = f"{dependency.project_name}\\n(missing)"
nodes.add(f'{dependency_key}["{dependency_label}"]:::missing')
edges.add(f"{package_key} -.-> {dependency_key}")
else:
edges.add(f'{package_key} -- "{edge_label}" --> {dependency_key}')
# Produce the Mermaid Markdown.
indent = " " * 4
output = dedent(
f"""\
flowchart TD
{indent}classDef missing stroke-dasharray: 5
"""
)
# Sort the nodes and edges to make the output deterministic.
output += indent
output += f"\n{indent}".join(node for node in sorted(nodes))
output += "\n" + indent
output += f"\n{indent}".join(edge for edge in sorted(edges))
output += "\n"
return output
def dump_graphviz(tree, output_format="dot", is_reverse=False):
"""Output dependency graph as one of the supported GraphViz output formats.
@@ -612,7 +711,11 @@ def dump_graphviz(tree, output_format="dot", is_reverse=False):
# Allow output of dot format, even if GraphViz isn't installed.
if output_format == "dot":
return graph.source
# Emulates graphviz.dot.Dot.__iter__() to force the sorting of graph.body.
# Fixes https://github.com/tox-dev/pipdeptree/issues/188
# That way we can guarantee the output of the dot format is deterministic
# and stable.
return "".join([tuple(graph)[0]] + sorted(graph.body) + [graph._tail])
# As it's unknown if the selected output format is binary or not, try to
# decode it as UTF8 and only print it out in binary if that's not possible.
@@ -742,12 +845,20 @@ def get_parser():
parser.add_argument(
"-p",
"--packages",
help="Comma separated list of select packages to show " "in the output. If set, --all will be ignored.",
help=(
"Comma separated list of select packages to show in the output. "
"Wildcards are supported, like 'somepackage.*'. "
"If set, --all will be ignored."
),
)
parser.add_argument(
"-e",
"--exclude",
help="Comma separated list of select packages to exclude " "from the output. If set, --all will be ignored.",
help=(
"Comma separated list of select packages to exclude from the output. "
"Wildcards are supported, like 'somepackage.*'. "
"If set, --all will be ignored."
),
metavar="PACKAGES",
)
parser.add_argument(
@@ -771,6 +882,12 @@ def get_parser():
"This option overrides all other options (except --json)."
),
)
parser.add_argument(
"--mermaid",
action="store_true",
default=False,
help=("Display dependency tree as a Maermaid graph. " "This option overrides all other options."),
)
parser.add_argument(
"--graph-output",
dest="output_format",
@@ -880,6 +997,8 @@ def main():
print(render_json(tree, indent=4))
elif args.json_tree:
print(render_json_tree(tree, indent=4))
elif args.mermaid:
print(render_mermaid(tree))
elif args.output_format:
output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse)
print_graphviz(output)
+2 -3
View File
@@ -1,5 +1,4 @@
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
__version__ = version = '2.3.1'
__version_tuple__ = version_tuple = (2, 3, 1)
__version__ = version = '2.7.0'
__version_tuple__ = version_tuple = (2, 7, 0)
+1 -1
View File
@@ -4,7 +4,7 @@ import os
from ._core import ShellDetectionFailure
__version__ = "1.5.0"
__version__ = "1.5.0.post1"
def detect_shell(pid=None, max_depth=10):
+27 -26
View File
@@ -1,31 +1,31 @@
from .api import TOMLDocument
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 dump
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 load
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 pipenv.vendor.tomlkit.api import TOMLDocument
from pipenv.vendor.tomlkit.api import aot
from pipenv.vendor.tomlkit.api import array
from pipenv.vendor.tomlkit.api import boolean
from pipenv.vendor.tomlkit.api import comment
from pipenv.vendor.tomlkit.api import date
from pipenv.vendor.tomlkit.api import datetime
from pipenv.vendor.tomlkit.api import document
from pipenv.vendor.tomlkit.api import dump
from pipenv.vendor.tomlkit.api import dumps
from pipenv.vendor.tomlkit.api import float_
from pipenv.vendor.tomlkit.api import inline_table
from pipenv.vendor.tomlkit.api import integer
from pipenv.vendor.tomlkit.api import item
from pipenv.vendor.tomlkit.api import key
from pipenv.vendor.tomlkit.api import key_value
from pipenv.vendor.tomlkit.api import load
from pipenv.vendor.tomlkit.api import loads
from pipenv.vendor.tomlkit.api import nl
from pipenv.vendor.tomlkit.api import parse
from pipenv.vendor.tomlkit.api import string
from pipenv.vendor.tomlkit.api import table
from pipenv.vendor.tomlkit.api import time
from pipenv.vendor.tomlkit.api import value
from pipenv.vendor.tomlkit.api import ws
__version__ = "0.9.2"
__version__ = "0.11.7"
__all__ = [
"aot",
"array",
@@ -49,6 +49,7 @@ __all__ = [
"string",
"table",
"time",
"TOMLDocument",
"value",
"ws",
]
+2 -3
View File
@@ -1,3 +1,4 @@
import contextlib
import sys
from typing import Any
@@ -15,9 +16,7 @@ def decode(string: Any, encodings: Optional[List[str]] = None):
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")
+31 -13
View File
@@ -6,9 +6,10 @@ 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 pipenv.vendor.tomlkit._compat import decode
RFC_3339_LOOSE = re.compile(
@@ -97,31 +98,48 @@ def parse_rfc3339(string: str) -> Union[datetime, date, time]:
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 escape_string(s: str) -> str:
def _unicode_escape(seq: str) -> str:
return "".join(f"\\u{ord(c):04x}" for c in seq)
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()
+50 -29
View File
@@ -3,33 +3,35 @@ 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 typing import Union
from ._utils import parse_rfc3339
from .container import Container
from .exceptions import UnexpectedCharError
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 DottedKey
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 SingleKey
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
from pipenv.vendor.tomlkit._utils import parse_rfc3339
from pipenv.vendor.tomlkit.container import Container
from pipenv.vendor.tomlkit.exceptions import UnexpectedCharError
from pipenv.vendor.tomlkit.items import AoT
from pipenv.vendor.tomlkit.items import Array
from pipenv.vendor.tomlkit.items import Bool
from pipenv.vendor.tomlkit.items import Comment
from pipenv.vendor.tomlkit.items import Date
from pipenv.vendor.tomlkit.items import DateTime
from pipenv.vendor.tomlkit.items import DottedKey
from pipenv.vendor.tomlkit.items import Float
from pipenv.vendor.tomlkit.items import InlineTable
from pipenv.vendor.tomlkit.items import Integer
from pipenv.vendor.tomlkit.items import Item as _Item
from pipenv.vendor.tomlkit.items import Key
from pipenv.vendor.tomlkit.items import SingleKey
from pipenv.vendor.tomlkit.items import String
from pipenv.vendor.tomlkit.items import StringType as _StringType
from pipenv.vendor.tomlkit.items import Table
from pipenv.vendor.tomlkit.items import Time
from pipenv.vendor.tomlkit.items import Trivia
from pipenv.vendor.tomlkit.items import Whitespace
from pipenv.vendor.tomlkit.items import item
from pipenv.vendor.tomlkit.parser import Parser
from pipenv.vendor.tomlkit.toml_document import TOMLDocument
def loads(string: Union[str, bytes]) -> TOMLDocument:
@@ -57,7 +59,7 @@ def dumps(data: Mapping, sort_keys: bool = False) -> str:
raise TypeError(msg) from ex
def load(fp: IO) -> TOMLDocument:
def load(fp: Union[IO[str], IO[bytes]]) -> TOMLDocument:
"""
Load toml document from a file-like object.
"""
@@ -104,9 +106,28 @@ def boolean(raw: str) -> Bool:
return item(raw == "true")
def string(raw: str) -> String:
"""Create a string item."""
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: str) -> Date:
@@ -154,7 +175,7 @@ def array(raw: str = None) -> Array:
return value(raw)
def table(is_super_table: bool = False) -> Table:
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
+107 -53
View File
@@ -8,22 +8,22 @@ 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 TOMLKitError
from .items import AoT
from .items import Comment
from .items import Item
from .items import Key
from .items import Null
from .items import SingleKey
from .items import Table
from .items import Trivia
from .items import Whitespace
from .items import _CustomDict
from .items import item as _item
from pipenv.vendor.tomlkit._compat import decode
from pipenv.vendor.tomlkit._utils import merge_dicts
from pipenv.vendor.tomlkit.exceptions import KeyAlreadyPresent
from pipenv.vendor.tomlkit.exceptions import NonExistentKey
from pipenv.vendor.tomlkit.exceptions import TOMLKitError
from pipenv.vendor.tomlkit.items import AoT
from pipenv.vendor.tomlkit.items import Comment
from pipenv.vendor.tomlkit.items import Item
from pipenv.vendor.tomlkit.items import Key
from pipenv.vendor.tomlkit.items import Null
from pipenv.vendor.tomlkit.items import SingleKey
from pipenv.vendor.tomlkit.items import Table
from pipenv.vendor.tomlkit.items import Trivia
from pipenv.vendor.tomlkit.items import Whitespace
from pipenv.vendor.tomlkit.items import _CustomDict
from pipenv.vendor.tomlkit.items import item as _item
_NOT_SET = object()
@@ -46,8 +46,27 @@ class Container(_CustomDict):
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 hasattr(v, "unwrap"):
v = v.unwrap()
if k in unwrapped:
merge_dicts(unwrapped[k], v)
else:
unwrapped[k] = v
return unwrapped
@property
def value(self) -> Dict[Any, Any]:
def value(self) -> Dict[str, Any]:
d = {}
for k, v in self._body:
if k is None:
@@ -173,9 +192,9 @@ class Container(_CustomDict):
item.name = key.key
prev = self._previous_item()
prev_ws = isinstance(prev, Whitespace) or ends_with_withespace(prev)
prev_ws = isinstance(prev, Whitespace) or ends_with_whitespace(prev)
if isinstance(item, Table):
if item.name != key.key:
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"
@@ -291,7 +310,7 @@ class Container(_CustomDict):
previous_item = self._body[-1][1]
if not (
isinstance(previous_item, Whitespace)
or ends_with_withespace(previous_item)
or ends_with_whitespace(previous_item)
or is_table
or "\n" in previous_item.trivia.trail
):
@@ -327,6 +346,25 @@ class Container(_CustomDict):
return self
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):
@@ -406,7 +444,7 @@ class Container(_CustomDict):
previous_item = self._body[idx - 1][1]
if not (
isinstance(previous_item, Whitespace)
or ends_with_withespace(previous_item)
or ends_with_whitespace(previous_item)
or isinstance(item, (AoT, Table))
or "\n" in previous_item.trivia.trail
):
@@ -487,7 +525,8 @@ class Container(_CustomDict):
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()
):
@@ -495,16 +534,21 @@ class Container(_CustomDict):
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):
@@ -545,14 +589,14 @@ class Container(_CustomDict):
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:
@@ -580,14 +624,14 @@ class Container(_CustomDict):
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}"
)
def __len__(self) -> int:
@@ -698,7 +742,7 @@ class Container(_CustomDict):
# - it is not the last item
last, _ = self._previous_item_with_index()
idx = last if idx < 0 else idx
has_ws = ends_with_withespace(value)
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"))
@@ -783,7 +827,7 @@ class OutOfOrderTableProxy(_CustomDict):
self._tables_map = {}
for i in indices:
key, item = self._container._body[i]
_, item = self._container._body[i]
if isinstance(item, Table):
self._tables.append(item)
@@ -794,6 +838,9 @@ class OutOfOrderTableProxy(_CustomDict):
if k is not None:
dict.__setitem__(self, k.key, v)
def unwrap(self) -> str:
return self._internal_container.unwrap()
@property
def value(self):
return self._internal_container.value
@@ -818,10 +865,20 @@ class OutOfOrderTableProxy(_CustomDict):
if key is not None:
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)
@@ -836,15 +893,12 @@ class OutOfOrderTableProxy(_CustomDict):
def __len__(self) -> int:
return dict.__len__(self)
def __getattr__(self, attribute):
return getattr(self._internal_container, attribute)
def setdefault(self, key: Union[Key, str], default: Any) -> Any:
super().setdefault(key, default=default)
return self[key]
def ends_with_withespace(it: Any) -> bool:
def ends_with_whitespace(it: Any) -> bool:
"""Returns ``True`` if the given item ``it`` is a ``Table`` or ``AoT`` object
ending with a ``Whitespace``.
"""
+14 -2
View File
@@ -1,3 +1,4 @@
from typing import Collection
from typing import Optional
@@ -193,6 +194,7 @@ class KeyAlreadyPresent(TOMLKitError):
"""
def __init__(self, key):
key = getattr(key, "key", key)
message = f'Key "{key}" already exists.'
super().__init__(message)
@@ -208,8 +210,18 @@ class InvalidControlChar(ParseError):
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().__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."
)
+505 -187
View File
File diff suppressed because it is too large Load Diff
+74 -65
View File
@@ -1,3 +1,4 @@
import datetime
import re
import string
@@ -7,48 +8,48 @@ from typing import Tuple
from typing import Type
from typing import Union
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 SingleKey
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 pipenv.vendor.tomlkit._compat import decode
from pipenv.vendor.tomlkit._utils import RFC_3339_LOOSE
from pipenv.vendor.tomlkit._utils import _escaped
from pipenv.vendor.tomlkit._utils import parse_rfc3339
from pipenv.vendor.tomlkit.container import Container
from pipenv.vendor.tomlkit.exceptions import EmptyKeyError
from pipenv.vendor.tomlkit.exceptions import EmptyTableNameError
from pipenv.vendor.tomlkit.exceptions import InternalParserError
from pipenv.vendor.tomlkit.exceptions import InvalidCharInStringError
from pipenv.vendor.tomlkit.exceptions import InvalidControlChar
from pipenv.vendor.tomlkit.exceptions import InvalidDateError
from pipenv.vendor.tomlkit.exceptions import InvalidDateTimeError
from pipenv.vendor.tomlkit.exceptions import InvalidNumberError
from pipenv.vendor.tomlkit.exceptions import InvalidTimeError
from pipenv.vendor.tomlkit.exceptions import InvalidUnicodeValueError
from pipenv.vendor.tomlkit.exceptions import ParseError
from pipenv.vendor.tomlkit.exceptions import UnexpectedCharError
from pipenv.vendor.tomlkit.exceptions import UnexpectedEofError
from pipenv.vendor.tomlkit.items import AoT
from pipenv.vendor.tomlkit.items import Array
from pipenv.vendor.tomlkit.items import Bool
from pipenv.vendor.tomlkit.items import BoolType
from pipenv.vendor.tomlkit.items import Comment
from pipenv.vendor.tomlkit.items import Date
from pipenv.vendor.tomlkit.items import DateTime
from pipenv.vendor.tomlkit.items import Float
from pipenv.vendor.tomlkit.items import InlineTable
from pipenv.vendor.tomlkit.items import Integer
from pipenv.vendor.tomlkit.items import Item
from pipenv.vendor.tomlkit.items import Key
from pipenv.vendor.tomlkit.items import KeyType
from pipenv.vendor.tomlkit.items import Null
from pipenv.vendor.tomlkit.items import SingleKey
from pipenv.vendor.tomlkit.items import String
from pipenv.vendor.tomlkit.items import StringType
from pipenv.vendor.tomlkit.items import Table
from pipenv.vendor.tomlkit.items import Time
from pipenv.vendor.tomlkit.items import Trivia
from pipenv.vendor.tomlkit.items import Whitespace
from pipenv.vendor.tomlkit.source import Source
from pipenv.vendor.tomlkit.toml_char import TOMLChar
from pipenv.vendor.tomlkit.toml_document import TOMLDocument
CTRL_I = 0x09 # Tab
@@ -146,7 +147,10 @@ class Parser:
key, value = item
if (key is not None and key.is_multi()) or not self._merge_ws(value, body):
# We actually have a table
body.append(key, value)
try:
body.append(key, value)
except Exception as e:
raise self.parse_error(ParseError, str(e)) from e
self.mark()
@@ -157,7 +161,10 @@ class Parser:
# along with it.
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)
@@ -226,7 +233,7 @@ class Parser:
# 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
@@ -464,6 +471,7 @@ class Parser:
# datetime
try:
dt = parse_rfc3339(raw)
assert isinstance(dt, datetime.datetime)
return DateTime(
dt.year,
dt.month,
@@ -482,17 +490,20 @@ class Parser:
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():
pass
time_raw = self.extract()
if not time_raw.strip():
trivia.comment_ws = time_raw
time_part = time_raw.rstrip()
trivia.comment_ws = time_raw[len(time_part) :]
if not time_part:
return date
dt = parse_rfc3339(raw + time_raw)
dt = parse_rfc3339(raw + time_part)
assert isinstance(dt, datetime.datetime)
return DateTime(
dt.year,
dt.month,
@@ -503,7 +514,7 @@ class Parser:
dt.microsecond,
dt.tzinfo,
trivia,
raw + time_raw,
raw + time_part,
)
except ValueError:
raise self.parse_error(InvalidDateError)
@@ -511,6 +522,7 @@ class Parser:
if m.group(5):
try:
t = parse_rfc3339(raw)
assert isinstance(t, datetime.time)
return Time(
t.hour,
t.minute,
@@ -672,10 +684,10 @@ class Parser:
or sign
and raw.startswith(".")
):
return
return None
if raw.startswith(("0o", "0x", "0b")) and sign:
return
return None
digits = "[0-9]"
base = 10
@@ -693,14 +705,14 @@ class Parser:
clean = re.sub(f"(?i)(?<={digits})_(?={digits})", "", raw).lower()
if "_" in clean:
return
return None
if (
clean.endswith(".")
or not clean.startswith("0x")
and clean.split("e", 1)[0].endswith(".")
):
return
return None
try:
return Integer(int(sign + clean, base), trivia, sign + raw)
@@ -708,7 +720,7 @@ class Parser:
try:
return Float(float(sign + clean), trivia, sign + raw)
except ValueError:
return
return None
def _parse_literal_string(self) -> String:
with self._state:
@@ -802,9 +814,7 @@ class Parser:
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 (
@@ -901,8 +911,6 @@ class Parser:
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.sep = ""
full_key = key
@@ -916,7 +924,7 @@ class Parser:
if 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
@@ -939,6 +947,7 @@ class Parser:
is_aot,
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:
@@ -960,10 +969,9 @@ class Parser:
key = name_parts[0]
for i, _name in enumerate(name_parts[1:]):
if _name in table:
child = table[_name]
else:
child = Table(
child = table.get(
_name,
Table(
Container(True),
Trivia(indent, cws, comment, trail),
is_aot and i == len(name_parts) - 2,
@@ -972,7 +980,8 @@ class Parser:
display_name=full_key.as_string()
if i == len(name_parts) - 2
else None,
)
),
)
if is_aot and i == len(name_parts) - 2:
table.raw_append(_name, AoT([child], name=table.name, parsed=True))
+4 -8
View File
@@ -4,9 +4,9 @@ from typing import Optional
from typing import Tuple
from typing import Type
from .exceptions import ParseError
from .exceptions import UnexpectedCharError
from .toml_char import TOMLChar
from pipenv.vendor.tomlkit.exceptions import ParseError
from pipenv.vendor.tomlkit.exceptions import UnexpectedCharError
from pipenv.vendor.tomlkit.toml_char import TOMLChar
class _State:
@@ -129,11 +129,7 @@ class Source(str):
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):
"""
+1 -9
View File
@@ -1,7 +1,5 @@
import string
from functools import lru_cache
class TOMLChar(str):
def __init__(self, c):
@@ -17,42 +15,36 @@ class TOMLChar(str):
NL = "\n\r"
WS = SPACES + NL
@lru_cache(maxsize=None)
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) -> 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) -> 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) -> bool:
"""
Whether the character is a whitespace character or not.
"""
return self in self.WS
@lru_cache(maxsize=None)
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) -> bool:
"""
Whether the character is a space or not
+1 -1
View File
@@ -1,4 +1,4 @@
from .container import Container
from pipenv.vendor.tomlkit.container import Container
class TOMLDocument(Container):
+40 -5
View File
@@ -1,5 +1,18 @@
from .api import loads
from .toml_document import TOMLDocument
import os
import re
from typing import TYPE_CHECKING
from pipenv.vendor.tomlkit.api import loads
from pipenv.vendor.tomlkit.toml_document import TOMLDocument
if TYPE_CHECKING:
from _typeshed import StrPath as _StrPath
else:
from typing import Union
_StrPath = Union[str, os.PathLike]
class TOMLFile:
@@ -9,15 +22,37 @@ class TOMLFile:
:param path: path to the TOML file
"""
def __init__(self, path: 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:
return loads(f.read())
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()
# apply linesep
if self._linesep == "\n":
content = content.replace("\r\n", "\n")
elif self._linesep == "\r\n":
content = re.sub(r"(?<!\r)\n", "\r\n", content)
with open(self._path, "w", encoding="utf-8", newline="") as f:
f.write(data.as_string())
f.write(content)
+7 -7
View File
@@ -1,20 +1,20 @@
attrs==22.1.0
attrs==23.1.0
cerberus==1.3.4
click-didyoumean==0.0.3
click==8.0.3
click-didyoumean==0.3.0
click==8.1.3
colorama==0.4.6
dparse==0.6.2
markupsafe==2.0.1
markupsafe==2.1.2
pep517==0.13.0
pexpect==4.8.0
pipdeptree==2.3.1
pipdeptree==2.7.0
plette[validation]==0.4.4
ptyprocess==0.7.0
python-dotenv==1.0.0
pythonfinder==1.3.2
requirementslib==2.2.4
ruamel.yaml==0.17.21
shellingham==1.5.0
shellingham==1.5.0.post1
toml==0.10.2
tomlkit==0.9.2
tomlkit==0.11.7
vistir==0.8.0