Merge pull request #4533 from pypa/vendor/update

Update vendored dependencies
This commit is contained in:
Frost Ming
2020-11-13 11:39:43 +08:00
committed by GitHub
161 changed files with 6873 additions and 3136 deletions
+11 -27
View File
@@ -58,7 +58,6 @@ Alternatively, you can set the ``PIPENV_PYPI_MIRROR`` environment variable.
☤ Injecting credentials into Pipfiles via environment variables
-----------------------------------------------------------------
Pipenv will expand environment variables (if defined) in your Pipfile. Quite
useful if you need to authenticate to a private PyPI::
@@ -76,6 +75,17 @@ If your credentials contain a special character, surround the references to the
[[source]]
url = "https://$USERNAME:'${PASSWORD}'@mypypi.example.com/simple"
Environment variables may be specified as ``${MY_ENVAR}`` or ``$MY_ENVAR``.
On Windows, ``%MY_ENVAR%`` is supported in addition to ``${MY_ENVAR}`` or ``$MY_ENVAR``.
Environment variables in the URL part of requirement specifiers can also be expanded, where the variable must be in the form of ``${VAR_NAME}``. Neither ``$VAR_NAME`` nor ``%VAR_NAME%`` is acceptable::
[[package]]
requests = {git = "git://${USERNAME}:${PASSWORD}@private.git.com/psf/requests.git", ref = "2.22.0"}
Keep in mind that environment variables are expanded in runtime, leaving the entries in ``Pipfile`` or ``Pipfile.lock`` untouched. This is to avoid the accidental leakage of credentials in the source code.
☤ Specifying Basically Anything
-------------------------------
@@ -436,32 +446,6 @@ You can then display the names and commands of your shortcuts by running ``pipen
command script
echospam echo I am really a very silly example
☤ Support for Environment Variables
-----------------------------------
Pipenv supports the usage of environment variables in place of authentication fragments
in your Pipfile. These will only be parsed if they are present in the ``[[source]]``
section. For example:
.. code-block:: toml
[[source]]
url = "https://${PYPI_USERNAME}:${PYPI_PASSWORD}@my_private_repo.example.com/simple"
verify_ssl = true
name = "pypi"
[dev-packages]
[packages]
requests = {version="*", index="home"}
maya = {version="*", index="pypi"}
records = "*"
Environment variables may be specified as ``${MY_ENVAR}`` or ``$MY_ENVAR``.
On Windows, ``%MY_ENVAR%`` is supported in addition to ``${MY_ENVAR}`` or ``$MY_ENVAR``.
.. _configuration-with-environment-variables:
☤ Configuration With Environment Variables
+1
View File
@@ -0,0 +1 @@
Support expanding environment variables in requirement URLs.
+25
View File
@@ -0,0 +1,25 @@
Update vendored dependencies:
- ``colorama`` from ``0.4.3`` to ``0.4.4``
- ``python-dotenv`` from ``0.10.3`` to ``0.15.0``
- ``first`` from ``2.0.1`` to ``2.0.2``
- ``iso8601`` from ``0.1.12`` to ``0.1.13``
- ``parse`` from ``1.15.0`` to ``1.18.0``
- ``pipdeptree`` from ``0.13.2`` to ``1.0.0``
- ``requests`` from ``2.23.0`` to ``2.25.0``
- ``idna`` from ``2.9`` to ``2.10``
- ``urllib3`` from ``1.25.9`` to ``1.26.1``
- ``certifi`` from ``2020.4.5.1`` to ``2020.11.8``
- ``requirementslib`` from ``1.5.15`` to ``1.5.16``
- ``attrs`` from ``19.3.0`` to ``20.3.0``
- ``distlib`` from ``0.3.0`` to ``0.3.1``
- ``packaging`` from ``20.3`` to ``20.4``
- ``six`` from ``1.14.0`` to ``1.15.0``
- ``semver`` from ``2.9.0`` to ``2.13.0``
- ``toml`` from ``0.10.1`` to ``0.10.2``
- ``cached-property`` from ``1.5.1`` to ``1.5.2``
- ``yaspin`` from ``0.14.3`` to ``1.2.0``
- ``resolvelib`` from ``0.3.0`` to ``0.5.2``
- ``pep517`` from ``0.8.2`` to ``0.9.1``
- ``zipp`` from ``0.6.0`` to ``1.2.0``
- ``importlib-metadata`` from ``1.6.0`` to ``2.0.0``
- ``importlib-resources`` from ``1.5.0`` to ``3.3.0``
+12 -4
View File
@@ -1,10 +1,12 @@
from __future__ import absolute_import, division, print_function
import sys
from functools import partial
from . import converters, exceptions, filters, validators
from . import converters, exceptions, filters, setters, validators
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, evolve, has
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
from ._make import (
NOTHING,
Attribute,
@@ -19,7 +21,7 @@ from ._make import (
from ._version_info import VersionInfo
__version__ = "19.3.0"
__version__ = "20.3.0"
__version_info__ = VersionInfo._from_version_string(__version__)
__title__ = "attrs"
@@ -39,7 +41,6 @@ s = attributes = attrs
ib = attr = attrib
dataclass = partial(attrs, auto_attribs=True) # happy Easter ;)
__all__ = [
"Attribute",
"Factory",
@@ -61,8 +62,15 @@ __all__ = [
"has",
"ib",
"make_class",
"resolve_types",
"s",
"set_run_validators",
"setters",
"validate",
"validators",
]
if sys.version_info[:2] >= (3, 6):
from ._next_gen import define, field, frozen, mutable
__all__.extend((define, field, frozen, mutable))
+170 -15
View File
@@ -18,6 +18,7 @@ from typing import (
from . import exceptions as exceptions
from . import filters as filters
from . import converters as converters
from . import setters as setters
from . import validators as validators
from ._version_info import VersionInfo
@@ -37,20 +38,26 @@ _T = TypeVar("_T")
_C = TypeVar("_C", bound=type)
_ValidatorType = Callable[[Any, Attribute[_T], _T], Any]
_ConverterType = Callable[[Any], _T]
_ConverterType = Callable[[Any], Any]
_FilterType = Callable[[Attribute[_T], _T], bool]
_ReprType = Callable[[Any], str]
_ReprArgType = Union[bool, _ReprType]
# 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.
_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any]
_OnSetAttrArgType = Union[
_OnSetAttrType, List[_OnSetAttrType], setters._NoOpType
]
_FieldTransformer = Callable[[type, List[Attribute]], List[Attribute]]
# 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]]]
# _make --
NOTHING: object
# NOTE: Factory lies about its return type to make this possible: `x: List[int] = Factory(list)`
# NOTE: Factory lies about its return type to make this possible:
# `x: List[int] # = Factory(list)`
# Work around mypy issue #4554 in the common case by using an overload.
@overload
def Factory(factory: Callable[[], _T]) -> _T: ...
@@ -70,16 +77,17 @@ class Attribute(Generic[_T]):
order: bool
hash: Optional[bool]
init: bool
converter: Optional[_ConverterType[_T]]
converter: Optional[_ConverterType]
metadata: Dict[Any, Any]
type: Optional[Type[_T]]
kw_only: bool
on_setattr: _OnSetAttrType
# NOTE: We had several choices for the annotation to use for type arg:
# 1) Type[_T]
# - Pros: Handles simple cases correctly
# - Cons: Might produce less informative errors in the case of conflicting TypeVars
# e.g. `attr.ib(default='bad', type=int)`
# - Cons: Might produce less informative errors in the case of conflicting
# TypeVars e.g. `attr.ib(default='bad', type=int)`
# 2) Callable[..., _T]
# - Pros: Better error messages than #1 for conflicting TypeVars
# - Cons: Terrible error messages for validator checks.
@@ -97,7 +105,8 @@ class Attribute(Generic[_T]):
# This makes this type of assignments possible:
# x: int = attr(8)
#
# This form catches explicit None or no default but with no other arguments returns Any.
# This form catches explicit None or no default but with no other arguments
# returns Any.
@overload
def attrib(
default: None = ...,
@@ -113,9 +122,11 @@ def attrib(
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> Any: ...
# This form catches an explicit None or no default and infers the type from the other arguments.
# This form catches an explicit None or no default and infers the type from the
# other arguments.
@overload
def attrib(
default: None = ...,
@@ -126,11 +137,12 @@ def attrib(
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: Optional[Type[_T]] = ...,
converter: Optional[_ConverterType[_T]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> _T: ...
# This form catches an explicit default argument.
@@ -144,11 +156,12 @@ def attrib(
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: Optional[Type[_T]] = ...,
converter: Optional[_ConverterType[_T]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any
@@ -162,11 +175,83 @@ def attrib(
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
type: object = ...,
converter: Optional[_ConverterType[_T]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> Any: ...
@overload
def field(
*,
default: None = ...,
validator: None = ...,
repr: _ReprArgType = ...,
hash: Optional[bool] = ...,
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
converter: None = ...,
factory: None = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> Any: ...
# This form catches an explicit None or no default and infers the type from the
# other arguments.
@overload
def field(
*,
default: None = ...,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: _ReprArgType = ...,
hash: Optional[bool] = ...,
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> _T: ...
# This form catches an explicit default argument.
@overload
def field(
*,
default: _T,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: _ReprArgType = ...,
hash: Optional[bool] = ...,
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> _T: ...
# This form covers type=non-Type: e.g. forward references (str), Any
@overload
def field(
*,
default: Optional[_T] = ...,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: _ReprArgType = ...,
hash: Optional[bool] = ...,
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
) -> Any: ...
@overload
def attrs(
@@ -187,6 +272,11 @@ def attrs(
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> _C: ...
@overload
def attrs(
@@ -207,7 +297,61 @@ def attrs(
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
auto_detect: bool = ...,
collect_by_mro: bool = ...,
getstate_setstate: Optional[bool] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> Callable[[_C], _C]: ...
@overload
def define(
maybe_cls: _C,
*,
these: Optional[Dict[str, Any]] = ...,
repr: 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] = ...,
) -> _C: ...
@overload
def define(
maybe_cls: None = ...,
*,
these: Optional[Dict[str, Any]] = ...,
repr: 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] = ...,
) -> Callable[[_C], _C]: ...
mutable = define
frozen = define # they differ only in their defaults
# TODO: add support for returning NamedTuple from the mypy plugin
class _Fields(Tuple[Attribute[Any], ...]):
@@ -216,9 +360,15 @@ class _Fields(Tuple[Attribute[Any], ...]):
def fields(cls: type) -> _Fields: ...
def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ...
def validate(inst: Any) -> None: ...
def resolve_types(
cls: _C,
globalns: Optional[Dict[str, Any]] = ...,
localns: Optional[Dict[str, Any]] = ...,
) -> _C: ...
# 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', [attr.ib()])` is valid
# we use Any instead of _CountingAttr so that e.g. `make_class('Foo',
# [attr.ib()])` is valid
def make_class(
name: str,
attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]],
@@ -238,12 +388,16 @@ def make_class(
auto_exc: bool = ...,
eq: Optional[bool] = ...,
order: Optional[bool] = ...,
collect_by_mro: bool = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
field_transformer: Optional[_FieldTransformer] = ...,
) -> type: ...
# _funcs --
# TODO: add support for returning TypedDict from the mypy plugin
# FIXME: asdict/astuple do not honor their factory args. waiting on one of these:
# FIXME: asdict/astuple do not honor their factory args. Waiting on one of
# these:
# https://github.com/python/mypy/issues/4236
# https://github.com/python/typing/issues/253
def asdict(
@@ -252,6 +406,7 @@ def asdict(
filter: Optional[_FilterType[Any]] = ...,
dict_factory: Type[Mapping[Any, Any]] = ...,
retain_collection_types: bool = ...,
value_serializer: Optional[Callable[[type, Attribute, Any], Any]] = ...,
) -> Dict[str, Any]: ...
# TODO: add support for returning NamedTuple from the mypy plugin
+4 -3
View File
@@ -19,9 +19,10 @@ else:
if PY2:
from UserDict import IterableUserDict
from collections import Mapping, Sequence
from UserDict import IterableUserDict
# We 'bundle' isclass instead of using inspect as importing inspect is
# fairly expensive (order of 10-15 ms for a modern machine in 2016)
def isclass(klass):
@@ -90,7 +91,7 @@ if PY2:
res.data.update(d) # We blocked update, so we have to do it like this.
return res
def just_warn(*args, **kw): # pragma: nocover
def just_warn(*args, **kw): # pragma: no cover
"""
We only warn on Python 3 because we are not aware of any concrete
consequences of not setting the cell on Python 2.
@@ -131,7 +132,7 @@ def make_set_closure_cell():
"""
# pypy makes this easy. (It also supports the logic below, but
# why not do the easy/fast thing?)
if PYPY: # pragma: no cover
if PYPY:
def set_closure_cell(cell, value):
cell.__setstate__((value,))
+112 -12
View File
@@ -13,6 +13,7 @@ def asdict(
filter=None,
dict_factory=dict,
retain_collection_types=False,
value_serializer=None,
):
"""
Return the ``attrs`` attribute values of *inst* as a dict.
@@ -32,6 +33,10 @@ def asdict(
:param bool retain_collection_types: Do not convert to ``list`` when
encountering an attribute whose type is ``tuple`` or ``set``. Only
meaningful if ``recurse`` is ``True``.
:param Optional[callable] value_serializer: A hook that is called for every
attribute or dict key/value. It receives the current instance, field
and value and must return the (updated) value. The hook is run *after*
the optional *filter* has been applied.
:rtype: return type of *dict_factory*
@@ -40,6 +45,7 @@ def asdict(
.. versionadded:: 16.0.0 *dict_factory*
.. versionadded:: 16.1.0 *retain_collection_types*
.. versionadded:: 20.3.0 *value_serializer*
"""
attrs = fields(inst.__class__)
rv = dict_factory()
@@ -47,17 +53,30 @@ def asdict(
v = getattr(inst, a.name)
if filter is not None and not filter(a, v):
continue
if value_serializer is not None:
v = value_serializer(inst, a, v)
if recurse is True:
if has(v.__class__):
rv[a.name] = asdict(
v, True, filter, dict_factory, retain_collection_types
v,
True,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
elif isinstance(v, (tuple, list, set)):
elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain_collection_types is True else list
rv[a.name] = cf(
[
_asdict_anything(
i, filter, dict_factory, retain_collection_types
i,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
for i in v
]
@@ -67,10 +86,18 @@ def asdict(
rv[a.name] = df(
(
_asdict_anything(
kk, filter, df, retain_collection_types
kk,
filter,
df,
retain_collection_types,
value_serializer,
),
_asdict_anything(
vv, filter, df, retain_collection_types
vv,
filter,
df,
retain_collection_types,
value_serializer,
),
)
for kk, vv in iteritems(v)
@@ -82,19 +109,36 @@ def asdict(
return rv
def _asdict_anything(val, filter, dict_factory, retain_collection_types):
def _asdict_anything(
val,
filter,
dict_factory,
retain_collection_types,
value_serializer,
):
"""
``asdict`` only works on attrs instances, this works on anything.
"""
if getattr(val.__class__, "__attrs_attrs__", None) is not None:
# Attrs class.
rv = asdict(val, True, filter, dict_factory, retain_collection_types)
elif isinstance(val, (tuple, list, set)):
rv = asdict(
val,
True,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
elif isinstance(val, (tuple, list, set, frozenset)):
cf = val.__class__ if retain_collection_types is True else list
rv = cf(
[
_asdict_anything(
i, filter, dict_factory, retain_collection_types
i,
filter,
dict_factory,
retain_collection_types,
value_serializer,
)
for i in val
]
@@ -103,13 +147,20 @@ def _asdict_anything(val, filter, dict_factory, retain_collection_types):
df = dict_factory
rv = df(
(
_asdict_anything(kk, filter, df, retain_collection_types),
_asdict_anything(vv, filter, df, retain_collection_types),
_asdict_anything(
kk, filter, df, retain_collection_types, value_serializer
),
_asdict_anything(
vv, filter, df, retain_collection_types, value_serializer
),
)
for kk, vv in iteritems(val)
)
else:
rv = val
if value_serializer is not None:
rv = value_serializer(None, None, rv)
return rv
@@ -164,7 +215,7 @@ def astuple(
retain_collection_types=retain,
)
)
elif isinstance(v, (tuple, list, set)):
elif isinstance(v, (tuple, list, set, frozenset)):
cf = v.__class__ if retain is True else list
rv.append(
cf(
@@ -209,6 +260,7 @@ def astuple(
rv.append(v)
else:
rv.append(v)
return rv if tuple_factory is list else tuple_factory(rv)
@@ -287,4 +339,52 @@ def evolve(inst, **changes):
init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
if init_name not in changes:
changes[init_name] = getattr(inst, attr_name)
return cls(**changes)
def resolve_types(cls, globalns=None, localns=None):
"""
Resolve any strings and forward annotations in type annotations.
This is only required if you need concrete types in `Attribute`'s *type*
field. In other words, you don't need to resolve your types if you only
use them for static type checking.
With no arguments, names will be looked up in the module in which the class
was created. If this is not what you want, e.g. if the name only exists
inside a method, you may pass *globalns* or *localns* to specify other
dictionaries in which to look up these names. See the docs of
`typing.get_type_hints` for more details.
:param type cls: Class to resolve.
:param Optional[dict] globalns: Dictionary containing global variables.
:param Optional[dict] localns: Dictionary containing local variables.
:raise TypeError: If *cls* is not a class.
:raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
class.
:raise NameError: If types cannot be resolved because of missing variables.
:returns: *cls* so you can use this function also as a class decorator.
Please note that you have to apply it **after** `attr.s`. That means
the decorator has to come in the line **before** `attr.s`.
.. versionadded:: 20.1.0
"""
try:
# Since calling get_type_hints is expensive we cache whether we've
# done it already.
cls.__attrs_types_resolved__
except AttributeError:
import typing
hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
for field in fields(cls):
if field.name in hints:
# Since fields have been frozen we must work around it.
_obj_setattr(field, "type", hints[field.name])
cls.__attrs_types_resolved__ = True
# Return the class so you can use it as a decorator too.
return cls
+923 -326
View File
File diff suppressed because it is too large Load Diff
+160
View File
@@ -0,0 +1,160 @@
"""
This is a Python 3.6 and later-only, keyword-only, and **provisional** API that
calls `attr.s` with different default values.
Provisional APIs that shall become "import attrs" one glorious day.
"""
from functools import partial
from attr.exceptions import UnannotatedAttributeError
from . import setters
from ._make import NOTHING, _frozen_setattrs, attrib, attrs
def define(
maybe_cls=None,
*,
these=None,
repr=None,
hash=None,
init=None,
slots=True,
frozen=False,
weakref_slot=True,
str=False,
auto_attribs=None,
kw_only=False,
cache_hash=False,
auto_exc=True,
eq=None,
order=False,
auto_detect=True,
getstate_setstate=None,
on_setattr=None,
field_transformer=None,
):
r"""
The only behavioral differences are the handling of the *auto_attribs*
option:
:param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves
exactly like `attr.s`. If left `None`, `attr.s` will try to guess:
1. If all attributes are annotated and no `attr.ib` is found, it assumes
*auto_attribs=True*.
2. Otherwise it assumes *auto_attribs=False* and tries to collect
`attr.ib`\ s.
and that mutable classes (``frozen=False``) validate on ``__setattr__``.
.. versionadded:: 20.1.0
"""
def do_it(cls, auto_attribs):
return attrs(
maybe_cls=cls,
these=these,
repr=repr,
hash=hash,
init=init,
slots=slots,
frozen=frozen,
weakref_slot=weakref_slot,
str=str,
auto_attribs=auto_attribs,
kw_only=kw_only,
cache_hash=cache_hash,
auto_exc=auto_exc,
eq=eq,
order=order,
auto_detect=auto_detect,
collect_by_mro=True,
getstate_setstate=getstate_setstate,
on_setattr=on_setattr,
field_transformer=field_transformer,
)
def wrap(cls):
"""
Making this a wrapper ensures this code runs during class creation.
We also ensure that frozen-ness of classes is inherited.
"""
nonlocal frozen, on_setattr
had_on_setattr = on_setattr not in (None, setters.NO_OP)
# By default, mutable classes validate on setattr.
if frozen is False and on_setattr is None:
on_setattr = setters.validate
# However, if we subclass a frozen class, we inherit the immutability
# and disable on_setattr.
for base_cls in cls.__bases__:
if base_cls.__setattr__ is _frozen_setattrs:
if had_on_setattr:
raise ValueError(
"Frozen classes can't use on_setattr "
"(frozen-ness was inherited)."
)
on_setattr = setters.NO_OP
break
if auto_attribs is not None:
return do_it(cls, auto_attribs)
try:
return do_it(cls, True)
except UnannotatedAttributeError:
return do_it(cls, False)
# maybe_cls's type depends on the usage of the decorator. It's a class
# if it's used as `@attrs` but ``None`` if used as `@attrs()`.
if maybe_cls is None:
return wrap
else:
return wrap(maybe_cls)
mutable = define
frozen = partial(define, frozen=True, on_setattr=None)
def field(
*,
default=NOTHING,
validator=None,
repr=True,
hash=None,
init=True,
metadata=None,
converter=None,
factory=None,
kw_only=False,
eq=None,
order=None,
on_setattr=None,
):
"""
Identical to `attr.ib`, except keyword-only and with some arguments
removed.
.. versionadded:: 20.1.0
"""
return attrib(
default=default,
validator=validator,
repr=repr,
hash=hash,
init=init,
metadata=metadata,
converter=converter,
factory=factory,
kw_only=kw_only,
eq=eq,
order=order,
on_setattr=on_setattr,
)
+8 -1
View File
@@ -4,7 +4,14 @@ Commonly useful converters.
from __future__ import absolute_import, division, print_function
from ._make import NOTHING, Factory
from ._make import NOTHING, Factory, pipe
__all__ = [
"pipe",
"optional",
"default_if_none",
]
def optional(converter):
+4 -5
View File
@@ -3,10 +3,9 @@ from . import _ConverterType
_T = TypeVar("_T")
def optional(
converter: _ConverterType[_T]
) -> _ConverterType[Optional[_T]]: ...
def pipe(*validators: _ConverterType) -> _ConverterType: ...
def optional(converter: _ConverterType) -> _ConverterType: ...
@overload
def default_if_none(default: _T) -> _ConverterType[_T]: ...
def default_if_none(default: _T) -> _ConverterType: ...
@overload
def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType[_T]: ...
def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ...
+22 -4
View File
@@ -1,20 +1,37 @@
from __future__ import absolute_import, division, print_function
class FrozenInstanceError(AttributeError):
class FrozenError(AttributeError):
"""
A frozen/immutable instance has been attempted to be modified.
A frozen/immutable instance or attribute haave been attempted to be
modified.
It mirrors the behavior of ``namedtuples`` by using the same error message
and subclassing `AttributeError`.
.. versionadded:: 16.1.0
.. versionadded:: 20.1.0
"""
msg = "can't set attribute"
args = [msg]
class FrozenInstanceError(FrozenError):
"""
A frozen instance has been attempted to be modified.
.. versionadded:: 16.1.0
"""
class FrozenAttributeError(FrozenError):
"""
A frozen attribute has been attempted to be modified.
.. versionadded:: 20.1.0
"""
class AttrsAttributeNotFoundError(ValueError):
"""
An ``attrs`` function couldn't find an attribute that the user asked for.
@@ -51,7 +68,8 @@ class UnannotatedAttributeError(RuntimeError):
class PythonTooOldError(RuntimeError):
"""
An ``attrs`` feature requiring a more recent python version has been used.
It was attempted to use an ``attrs`` feature that requires a newer Python
version.
.. versionadded:: 18.2.0
"""
+3 -1
View File
@@ -1,8 +1,10 @@
from typing import Any
class FrozenInstanceError(AttributeError):
class FrozenError(AttributeError):
msg: str = ...
class FrozenInstanceError(FrozenError): ...
class FrozenAttributeError(FrozenError): ...
class AttrsAttributeNotFoundError(ValueError): ...
class NotAnAttrsClassError(ValueError): ...
class DefaultAlreadySetError(RuntimeError): ...
+77
View File
@@ -0,0 +1,77 @@
"""
Commonly used hooks for on_setattr.
"""
from __future__ import absolute_import, division, print_function
from . import _config
from .exceptions import FrozenAttributeError
def pipe(*setters):
"""
Run all *setters* and return the return value of the last one.
.. versionadded:: 20.1.0
"""
def wrapped_pipe(instance, attrib, new_value):
rv = new_value
for setter in setters:
rv = setter(instance, attrib, rv)
return rv
return wrapped_pipe
def frozen(_, __, ___):
"""
Prevent an attribute to be modified.
.. versionadded:: 20.1.0
"""
raise FrozenAttributeError()
def validate(instance, attrib, new_value):
"""
Run *attrib*'s validator on *new_value* if it has one.
.. versionadded:: 20.1.0
"""
if _config._run_validators is False:
return new_value
v = attrib.validator
if not v:
return new_value
v(instance, attrib, new_value)
return new_value
def convert(instance, attrib, new_value):
"""
Run *attrib*'s converter -- if it has one -- on *new_value* and return the
result.
.. versionadded:: 20.1.0
"""
c = attrib.converter
if c:
return c(new_value)
return new_value
NO_OP = object()
"""
Sentinel for disabling class-wide *on_setattr* hooks for certain attributes.
Does not work in `pipe` or within lists.
.. versionadded:: 20.1.0
"""
+18
View File
@@ -0,0 +1,18 @@
from . import _OnSetAttrType, Attribute
from typing import TypeVar, Any, NewType, NoReturn, cast
_T = TypeVar("_T")
def frozen(
instance: Any, attribute: Attribute, new_value: Any
) -> NoReturn: ...
def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ...
def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ...
# convert is allowed to return Any, because they can be chained using pipe.
def convert(
instance: Any, attribute: Attribute[Any], new_value: Any
) -> Any: ...
_NoOpType = NewType("_NoOpType", object)
NO_OP: _NoOpType
+3 -2
View File
@@ -67,7 +67,7 @@ def instance_of(type):
return _InstanceOfValidator(type)
@attrs(repr=False, frozen=True)
@attrs(repr=False, frozen=True, slots=True)
class _MatchesReValidator(object):
regex = attrib()
flags = attrib()
@@ -171,7 +171,8 @@ def provides(interface):
performed using ``interface.providedBy(value)`` (see `zope.interface
<https://zopeinterface.readthedocs.io/en/latest/>`_).
:param zope.interface.Interface interface: The interface to check for.
:param interface: The interface to check for.
:type interface: ``zope.interface.Interface``
:raises TypeError: With a human readable error message, the attribute
(of type `attr.Attribute`), the expected interface, and the
+3 -2
View File
@@ -2,9 +2,10 @@
__author__ = "Daniel Greenfeld"
__email__ = "pydanny@gmail.com"
__version__ = "1.5.1"
__version__ = "1.5.2"
__license__ = "BSD"
from functools import wraps
from time import time
import threading
@@ -36,7 +37,7 @@ class cached_property(object):
return value
def _wrap_in_coroutine(self, obj):
@wraps(obj)
@asyncio.coroutine
def wrapper():
future = asyncio.ensure_future(self.func(obj))
+1 -1
View File
@@ -1,3 +1,3 @@
from .core import contents, where
__version__ = "2020.04.05.1"
__version__ = "2020.11.08"
+216 -251
View File
@@ -58,38 +58,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
# Label: "Verisign Class 3 Public Primary Certification Authority - G3"
# Serial: 206684696279472310254277870180966723415
# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09
# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6
# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Label: "Entrust.net Premium 2048 Secure Server CA"
@@ -152,39 +120,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----
# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network
# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network
# Label: "AddTrust External Root"
# Serial: 1
# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f
# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68
# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----
# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
# Label: "Entrust Root Certification Authority"
@@ -640,46 +575,6 @@ VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----
# Issuer: O=Government Root Certification Authority
# Subject: O=Government Root Certification Authority
# Label: "Taiwan GRCA"
# Serial: 42023070807708724159991140556527066870
# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e
# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9
# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
pYYsfPQS
-----END CERTIFICATE-----
# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
# Label: "DigiCert Assured ID Root CA"
@@ -1127,38 +1022,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----
# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed
# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed
# Label: "OISTE WISeKey Global Root GA CA"
# Serial: 86718877871133159090080555911823548314
# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93
# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9
# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
/L7fCg0=
-----END CERTIFICATE-----
# Issuer: CN=Certigna O=Dhimyotis
# Subject: CN=Certigna O=Dhimyotis
# Label: "Certigna"
@@ -1499,47 +1362,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden
# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden
# Label: "Staat der Nederlanden Root CA - G2"
# Serial: 10000012
# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a
# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16
# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f
-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
-----END CERTIFICATE-----
# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post
# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post
# Label: "Hongkong Post Root CA 1"
@@ -2391,38 +2213,6 @@ e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
TpPDpFQUWw==
-----END CERTIFICATE-----
# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus
# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus
# Label: "EE Certification Centre Root CA"
# Serial: 112324828676200291871926431888494945866
# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f
# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7
# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
-----END CERTIFICATE-----
# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
# Label: "D-TRUST Root Class 3 CA 2 2009"
@@ -3788,47 +3578,6 @@ CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A.
# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A.
# Label: "LuxTrust Global Root 2"
# Serial: 59914338225734147123941058376788110305822489521
# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c
# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f
# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5
-----BEGIN CERTIFICATE-----
MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL
BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV
BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw
MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B
LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F
ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem
hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1
EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn
Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4
zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ
96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m
j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g
DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+
8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j
X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH
hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB
KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0
Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT
+Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL
BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9
BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO
jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9
loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c
qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+
2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/
JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre
zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf
LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+
x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6
oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr
-----END CERTIFICATE-----
# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1"
@@ -4639,3 +4388,219 @@ IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk
5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY
n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----
# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
# Label: "Microsoft ECC Root Certificate Authority 2017"
# Serial: 136839042543790627607696632466672567020
# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67
# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5
# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy
b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR
ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb
hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3
FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV
L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB
iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----
# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
# Label: "Microsoft RSA Root Certificate Authority 2017"
# Serial: 40975477897264996090493496164228220339
# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47
# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74
# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N
aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ
Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0
ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1
HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm
gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ
jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc
aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG
YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6
W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K
UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH
+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q
W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC
LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC
gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6
tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh
SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2
TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3
pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR
xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp
GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9
dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN
AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
RA+GsCyRxj3qrg+E
-----END CERTIFICATE-----
# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
# Label: "e-Szigno Root CA 2017"
# Serial: 411379200276854331539784714
# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98
# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1
# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ
BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg
THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v
IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv
xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H
Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB
eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo
jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
+efcMQ==
-----END CERTIFICATE-----
# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2
# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2
# Label: "certSIGN Root CA G2"
# Serial: 313609486401300475190
# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7
# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32
# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ
R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF
dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw
vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ
uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp
n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs
cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW
xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P
rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF
DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx
DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy
LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C
eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ
d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq
kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl
qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0
OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c
NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk
ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO
pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj
03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk
PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE
1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX
QRBdJ3NghVdJIgc=
-----END CERTIFICATE-----
# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc.
# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc.
# Label: "Trustwave Global Certification Authority"
# Serial: 1846098327275375458322922162
# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e
# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5
# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8
-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw
CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x
ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1
c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx
OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI
SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn
swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu
7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8
1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW
80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP
JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l
RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw
hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10
coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc
BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n
twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud
DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W
0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe
uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q
lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB
aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE
sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT
MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe
qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh
VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8
h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9
EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK
yeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----
# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc.
# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc.
# Label: "Trustwave Global ECC P256 Certification Authority"
# Serial: 4151900041497450638097112925
# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54
# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf
# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4
-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G
A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0
d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF
Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN
FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w
DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw
CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh
DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----
# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc.
# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc.
# Label: "Trustwave Global ECC P384 Certification Authority"
# Serial: 2704997926503831671788816187
# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6
# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2
# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97
-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G
A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0
d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF
Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB
BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ
j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF
1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G
A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3
AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC
MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu
Sw==
-----END CERTIFICATE-----
+35 -5
View File
@@ -9,7 +9,36 @@ This module returns the installation location of cacert.pem or its contents.
import os
try:
from importlib.resources import read_text
from importlib.resources import path as get_path, read_text
_CACERT_CTX = None
_CACERT_PATH = None
def where():
# This is slightly terrible, but we want to delay extracting the file
# in cases where we're inside of a zipimport situation until someone
# actually calls where(), but we don't want to re-extract the file
# on every call of where(), so we'll do it once then store it in a
# global variable.
global _CACERT_CTX
global _CACERT_PATH
if _CACERT_PATH is None:
# This is slightly janky, the importlib.resources API wants you to
# manage the cleanup of this file, so it doesn't actually return a
# path, it returns a context manager that will give you the path
# when you enter it and will do any cleanup when you leave it. In
# the common case of not needing a temporary file, it will just
# return the file system location and the __exit__() is a no-op.
#
# We also have to hold onto the actual context manager, because
# it will do the cleanup whenever it gets garbage collected, so
# we will also store that at the global level as well.
_CACERT_CTX = get_path("certifi", "cacert.pem")
_CACERT_PATH = str(_CACERT_CTX.__enter__())
return _CACERT_PATH
except ImportError:
# This fallback will work for Python versions prior to 3.7 that lack the
# importlib.resources module but relies on the existing `where` function
@@ -19,11 +48,12 @@ except ImportError:
with open(where(), "r", encoding=encoding) as data:
return data.read()
# If we don't have importlib.resources, then we will just do the old logic
# of assuming we're on the filesystem and munge the path directly.
def where():
f = os.path.dirname(__file__)
def where():
f = os.path.dirname(__file__)
return os.path.join(f, "cacert.pem")
return os.path.join(f, "cacert.pem")
def contents():
+1 -1
View File
@@ -3,4 +3,4 @@ from .initialise import init, deinit, reinit, colorama_text
from .ansi import Fore, Back, Style, Cursor
from .ansitowin32 import AnsiToWin32
__version__ = '0.4.3'
__version__ = '0.4.4'
+1 -1
View File
@@ -6,7 +6,7 @@ See: http://en.wikipedia.org/wiki/ANSI_escape_code
CSI = '\033['
OSC = '\033]'
BEL = '\007'
BEL = '\a'
def code_to_chars(code):
+10 -9
View File
@@ -3,7 +3,7 @@ import re
import sys
import os
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL
from .winterm import WinTerm, WinColor, WinStyle
from .win32 import windll, winapi_test
@@ -68,7 +68,7 @@ class AnsiToWin32(object):
win32 function calls.
'''
ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
ANSI_OSC_RE = re.compile('\001?\033\\]((?:.|;)*?)(\x07)\002?') # Operating System Command
ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
# The wrapped stream (normally sys.stdout or sys.stderr)
@@ -247,11 +247,12 @@ class AnsiToWin32(object):
start, end = match.span()
text = text[:start] + text[end:]
paramstring, command = match.groups()
if command in '\x07': # \x07 = BEL
params = paramstring.split(";")
# 0 - change title and icon (we will only change title)
# 1 - change icon (we don't support this)
# 2 - change title
if params[0] in '02':
winterm.set_title(params[1])
if command == BEL:
if paramstring.count(";") == 1:
params = paramstring.split(";")
# 0 - change title and icon (we will only change title)
# 1 - change icon (we don't support this)
# 2 - change title
if params[0] in '02':
winterm.set_title(params[1])
return text
+1 -1
View File
@@ -6,7 +6,7 @@
#
import logging
__version__ = '0.3.0'
__version__ = '0.3.1'
class DistlibException(Exception):
pass
+6 -3
View File
@@ -14,7 +14,10 @@ import sys
import stat
from os.path import abspath
import fnmatch
import collections
try:
from collections.abc import Callable
except ImportError:
from collections import Callable
import errno
from . import tarfile
@@ -528,7 +531,7 @@ def register_archive_format(name, function, extra_args=None, description=''):
"""
if extra_args is None:
extra_args = []
if not isinstance(function, collections.Callable):
if not isinstance(function, Callable):
raise TypeError('The %s object is not callable' % function)
if not isinstance(extra_args, (tuple, list)):
raise TypeError('extra_args needs to be a sequence')
@@ -621,7 +624,7 @@ def _check_unpack_options(extensions, function, extra_args):
raise RegistryError(msg % (extension,
existing_extensions[extension]))
if not isinstance(function, collections.Callable):
if not isinstance(function, Callable):
raise TypeError('The registered function must be a callable')
+1 -1
View File
@@ -319,7 +319,7 @@ except ImportError: # pragma: no cover
try:
callable = callable
except NameError: # pragma: no cover
from collections import Callable
from collections.abc import Callable
def callable(obj):
return isinstance(obj, Callable)
+1 -1
View File
@@ -550,7 +550,7 @@ class InstalledDistribution(BaseInstalledDistribution):
r = finder.find(WHEEL_METADATA_FILENAME)
# Temporary - for legacy support
if r is None:
r = finder.find('METADATA')
r = finder.find(LEGACY_METADATA_FILENAME)
if r is None:
raise ValueError('no %s found in %s' % (METADATA_FILENAME,
path))
+41 -81
View File
@@ -5,7 +5,7 @@
#
"""Implementation of the Metadata for Python packages PEPs.
Supports all metadata formats (1.0, 1.1, 1.2, and 2.0 experimental).
Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and withdrawn 2.0).
"""
from __future__ import unicode_literals
@@ -194,38 +194,12 @@ def _best_version(fields):
return '2.0'
# This follows the rules about transforming keys as described in
# https://www.python.org/dev/peps/pep-0566/#id17
_ATTR2FIELD = {
'metadata_version': 'Metadata-Version',
'name': 'Name',
'version': 'Version',
'platform': 'Platform',
'supported_platform': 'Supported-Platform',
'summary': 'Summary',
'description': 'Description',
'keywords': 'Keywords',
'home_page': 'Home-page',
'author': 'Author',
'author_email': 'Author-email',
'maintainer': 'Maintainer',
'maintainer_email': 'Maintainer-email',
'license': 'License',
'classifier': 'Classifier',
'download_url': 'Download-URL',
'obsoletes_dist': 'Obsoletes-Dist',
'provides_dist': 'Provides-Dist',
'requires_dist': 'Requires-Dist',
'setup_requires_dist': 'Setup-Requires-Dist',
'requires_python': 'Requires-Python',
'requires_external': 'Requires-External',
'requires': 'Requires',
'provides': 'Provides',
'obsoletes': 'Obsoletes',
'project_url': 'Project-URL',
'private_version': 'Private-Version',
'obsoleted_by': 'Obsoleted-By',
'extension': 'Extension',
'provides_extra': 'Provides-Extra',
name.lower().replace("-", "_"): name for name in _ALL_FIELDS
}
_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()}
_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist')
_VERSIONS_FIELDS = ('Requires-Python',)
@@ -262,7 +236,7 @@ def _get_name_and_version(name, version, for_filename=False):
class LegacyMetadata(object):
"""The legacy metadata of a release.
Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can
Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can
instantiate the class with one of these arguments (or none):
- *path*, the path to a metadata file
- *fileobj* give a file-like object with metadata as content
@@ -381,6 +355,11 @@ class LegacyMetadata(object):
value = msg[field]
if value is not None and value != 'UNKNOWN':
self.set(field, value)
# PEP 566 specifies that the body be used for the description, if
# available
body = msg.get_payload()
self["Description"] = body if body else self["Description"]
# logger.debug('Attempting to set metadata for %s', self)
# self.set_metadata_version()
@@ -567,57 +546,21 @@ class LegacyMetadata(object):
Field names will be converted to use the underscore-lowercase style
instead of hyphen-mixed case (i.e. home_page instead of Home-page).
This is as per https://www.python.org/dev/peps/pep-0566/#id17.
"""
self.set_metadata_version()
mapping_1_0 = (
('metadata_version', 'Metadata-Version'),
('name', 'Name'),
('version', 'Version'),
('summary', 'Summary'),
('home_page', 'Home-page'),
('author', 'Author'),
('author_email', 'Author-email'),
('license', 'License'),
('description', 'Description'),
('keywords', 'Keywords'),
('platform', 'Platform'),
('classifiers', 'Classifier'),
('download_url', 'Download-URL'),
)
fields = _version2fieldlist(self['Metadata-Version'])
data = {}
for key, field_name in mapping_1_0:
for field_name in fields:
if not skip_missing or field_name in self._fields:
data[key] = self[field_name]
if self['Metadata-Version'] == '1.2':
mapping_1_2 = (
('requires_dist', 'Requires-Dist'),
('requires_python', 'Requires-Python'),
('requires_external', 'Requires-External'),
('provides_dist', 'Provides-Dist'),
('obsoletes_dist', 'Obsoletes-Dist'),
('project_url', 'Project-URL'),
('maintainer', 'Maintainer'),
('maintainer_email', 'Maintainer-email'),
)
for key, field_name in mapping_1_2:
if not skip_missing or field_name in self._fields:
if key != 'project_url':
data[key] = self[field_name]
else:
data[key] = [','.join(u) for u in self[field_name]]
elif self['Metadata-Version'] == '1.1':
mapping_1_1 = (
('provides', 'Provides'),
('requires', 'Requires'),
('obsoletes', 'Obsoletes'),
)
for key, field_name in mapping_1_1:
if not skip_missing or field_name in self._fields:
key = _FIELD2ATTR[field_name]
if key != 'project_url':
data[key] = self[field_name]
else:
data[key] = [','.join(u) for u in self[field_name]]
return data
@@ -1003,10 +946,14 @@ class Metadata(object):
LEGACY_MAPPING = {
'name': 'Name',
'version': 'Version',
'license': 'License',
('extensions', 'python.details', 'license'): 'License',
'summary': 'Summary',
'description': 'Description',
'classifiers': 'Classifier',
('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page',
('extensions', 'python.project', 'contacts', 0, 'name'): 'Author',
('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email',
'source_url': 'Download-URL',
('extensions', 'python.details', 'classifiers'): 'Classifier',
}
def _to_legacy(self):
@@ -1034,16 +981,29 @@ class Metadata(object):
assert self._data and not self._legacy
result = LegacyMetadata()
nmd = self._data
# import pdb; pdb.set_trace()
for nk, ok in self.LEGACY_MAPPING.items():
if nk in nmd:
result[ok] = nmd[nk]
if not isinstance(nk, tuple):
if nk in nmd:
result[ok] = nmd[nk]
else:
d = nmd
found = True
for k in nk:
try:
d = d[k]
except (KeyError, IndexError):
found = False
break
if found:
result[ok] = d
r1 = process_entries(self.run_requires + self.meta_requires)
r2 = process_entries(self.build_requires + self.dev_requires)
if self.extras:
result['Provides-Extra'] = sorted(self.extras)
result['Requires-Dist'] = sorted(r1)
result['Setup-Requires-Dist'] = sorted(r2)
# TODO: other fields such as contacts
# TODO: any other fields wanted
return result
def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True):
+8 -5
View File
@@ -48,7 +48,7 @@ if __name__ == '__main__':
'''
def _enquote_executable(executable):
def enquote_executable(executable):
if ' ' in executable:
# make sure we quote only the executable in case of env
# for example /usr/bin/env "/dir with spaces/bin/jython"
@@ -63,6 +63,8 @@ def _enquote_executable(executable):
executable = '"%s"' % executable
return executable
# Keep the old name around (for now), as there is at least one project using it!
_enquote_executable = enquote_executable
class ScriptMaker(object):
"""
@@ -88,6 +90,7 @@ class ScriptMaker(object):
self._is_nt = os.name == 'nt' or (
os.name == 'java' and os._name == 'nt')
self.version_info = sys.version_info
def _get_alternate_executable(self, executable, options):
if options.get('gui', False) and self._is_nt: # pragma: no cover
@@ -185,7 +188,7 @@ class ScriptMaker(object):
# If the user didn't specify an executable, it may be necessary to
# cater for executable paths with spaces (not uncommon on Windows)
if enquote:
executable = _enquote_executable(executable)
executable = enquote_executable(executable)
# Issue #51: don't use fsencode, since we later try to
# check that the shebang is decodable using utf-8.
executable = executable.encode('utf-8')
@@ -293,10 +296,10 @@ class ScriptMaker(object):
if '' in self.variants:
scriptnames.add(name)
if 'X' in self.variants:
scriptnames.add('%s%s' % (name, sys.version_info[0]))
scriptnames.add('%s%s' % (name, self.version_info[0]))
if 'X.Y' in self.variants:
scriptnames.add('%s-%s.%s' % (name, sys.version_info[0],
sys.version_info[1]))
scriptnames.add('%s-%s.%s' % (name, self.version_info[0],
self.version_info[1]))
if options and options.get('gui', False):
ext = 'pyw'
else:
+26 -12
View File
@@ -26,7 +26,8 @@ import zipfile
from . import __version__, DistlibException
from .compat import sysconfig, ZipFile, fsdecode, text_type, filter
from .database import InstalledDistribution
from .metadata import Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME
from .metadata import (Metadata, METADATA_FILENAME, WHEEL_METADATA_FILENAME,
LEGACY_METADATA_FILENAME)
from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache,
cached_property, get_cache_base, read_exports, tempdir)
from .version import NormalizedVersion, UnsupportedVersionError
@@ -221,10 +222,12 @@ class Wheel(object):
wheel_metadata = self.get_wheel_metadata(zf)
wv = wheel_metadata['Wheel-Version'].split('.', 1)
file_version = tuple([int(i) for i in wv])
if file_version < (1, 1):
fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME, 'METADATA']
else:
fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME]
# if file_version < (1, 1):
# fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME,
# LEGACY_METADATA_FILENAME]
# else:
# fns = [WHEEL_METADATA_FILENAME, METADATA_FILENAME]
fns = [WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME]
result = None
for fn in fns:
try:
@@ -299,10 +302,9 @@ class Wheel(object):
return hash_kind, result
def write_record(self, records, record_path, base):
records = list(records) # make a copy for sorting
records = list(records) # make a copy, as mutated
p = to_posix(os.path.relpath(record_path, base))
records.append((p, '', ''))
records.sort()
with CSVWriter(record_path) as writer:
for row in records:
writer.writerow(row)
@@ -425,6 +427,18 @@ class Wheel(object):
ap = to_posix(os.path.join(info_dir, 'WHEEL'))
archive_paths.append((ap, p))
# sort the entries by archive path. Not needed by any spec, but it
# keeps the archive listing and RECORD tidier than they would otherwise
# be. Use the number of path segments to keep directory entries together,
# and keep the dist-info stuff at the end.
def sorter(t):
ap = t[0]
n = ap.count('/')
if '.dist-info' in ap:
n += 10000
return (n, ap)
archive_paths = sorted(archive_paths, key=sorter)
# Now, at last, RECORD.
# Paths in here are archive paths - nothing else makes sense.
self.write_records((distinfo, info_dir), libdir, archive_paths)
@@ -476,7 +490,7 @@ class Wheel(object):
data_dir = '%s.data' % name_ver
info_dir = '%s.dist-info' % name_ver
metadata_name = posixpath.join(info_dir, METADATA_FILENAME)
metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME)
wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
record_name = posixpath.join(info_dir, 'RECORD')
@@ -619,7 +633,7 @@ class Wheel(object):
for v in epdata[k].values():
s = '%s:%s' % (v.prefix, v.suffix)
if v.flags:
s += ' %s' % v.flags
s += ' [%s]' % ','.join(v.flags)
d[v.name] = s
except Exception:
logger.warning('Unable to read legacy script '
@@ -773,7 +787,7 @@ class Wheel(object):
data_dir = '%s.data' % name_ver
info_dir = '%s.dist-info' % name_ver
metadata_name = posixpath.join(info_dir, METADATA_FILENAME)
metadata_name = posixpath.join(info_dir, LEGACY_METADATA_FILENAME)
wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
record_name = posixpath.join(info_dir, 'RECORD')
@@ -842,7 +856,7 @@ class Wheel(object):
def get_version(path_map, info_dir):
version = path = None
key = '%s/%s' % (info_dir, METADATA_FILENAME)
key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME)
if key not in path_map:
key = '%s/PKG-INFO' % info_dir
if key in path_map:
@@ -868,7 +882,7 @@ class Wheel(object):
if updated:
md = Metadata(path=path)
md.version = updated
legacy = not path.endswith(METADATA_FILENAME)
legacy = path.endswith(LEGACY_METADATA_FILENAME)
md.write(path=path, legacy=legacy)
logger.debug('Version updated from %r to %r', version,
updated)
+29 -8
View File
@@ -9,7 +9,7 @@ except ImportError:
'Run pip install "python-dotenv[cli]" to fix this.')
sys.exit(1)
from .compat import IS_TYPE_CHECKING
from .compat import IS_TYPE_CHECKING, to_env
from .main import dotenv_values, get_key, set_key, unset_key
from .version import __version__
@@ -19,19 +19,23 @@ if IS_TYPE_CHECKING:
@click.group()
@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'),
type=click.Path(exists=True),
type=click.Path(file_okay=True),
help="Location of the .env file, defaults to .env file in current working directory.")
@click.option('-q', '--quote', default='always',
type=click.Choice(['always', 'never', 'auto']),
help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.")
@click.option('-e', '--export', default=False,
type=click.BOOL,
help="Whether to write the dot file as an executable bash script.")
@click.version_option(version=__version__)
@click.pass_context
def cli(ctx, file, quote):
# type: (click.Context, Any, Any) -> None
def cli(ctx, file, quote, export):
# type: (click.Context, Any, Any, Any) -> None
'''This script is used to set, get or unset values from a .env file.'''
ctx.obj = {}
ctx.obj['FILE'] = file
ctx.obj['QUOTE'] = quote
ctx.obj['EXPORT'] = export
ctx.obj['FILE'] = file
@cli.command()
@@ -40,6 +44,11 @@ def list(ctx):
# type: (click.Context) -> None
'''Display all the stored key/value.'''
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
'Path "%s" does not exist.' % (file),
ctx=ctx
)
dotenv_as_dict = dotenv_values(file)
for k, v in dotenv_as_dict.items():
click.echo('%s=%s' % (k, v))
@@ -54,7 +63,8 @@ def set(ctx, key, value):
'''Store the given key/value.'''
file = ctx.obj['FILE']
quote = ctx.obj['QUOTE']
success, key, value = set_key(file, key, value, quote)
export = ctx.obj['EXPORT']
success, key, value = set_key(file, key, value, quote, export)
if success:
click.echo('%s=%s' % (key, value))
else:
@@ -68,6 +78,11 @@ def get(ctx, key):
# type: (click.Context, Any) -> None
'''Retrieve the value for the given key.'''
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
'Path "%s" does not exist.' % (file),
ctx=ctx
)
stored_value = get_key(file, key)
if stored_value:
click.echo('%s=%s' % (key, stored_value))
@@ -97,11 +112,17 @@ def run(ctx, commandline):
# type: (click.Context, List[str]) -> None
"""Run command with environment variables present."""
file = ctx.obj['FILE']
dotenv_as_dict = dotenv_values(file)
if not os.path.isfile(file):
raise click.BadParameter(
'Invalid value for \'-f\' "%s" does not exist.' % (file),
ctx=ctx
)
dotenv_as_dict = {to_env(k): to_env(v) for (k, v) in dotenv_values(file).items() if v is not None}
if not commandline:
click.echo('No command given.')
exit(1)
ret = run_command(commandline, dotenv_as_dict) # type: ignore
ret = run_command(commandline, dotenv_as_dict)
exit(ret)
+1 -1
View File
@@ -12,7 +12,7 @@ def is_type_checking():
# type: () -> bool
try:
from typing import TYPE_CHECKING
except ImportError: # pragma: no cover
except ImportError:
return False
return TYPE_CHECKING
+85 -52
View File
@@ -2,21 +2,23 @@
from __future__ import absolute_import, print_function, unicode_literals
import io
import logging
import os
import re
import shutil
import sys
import tempfile
import warnings
from collections import OrderedDict
from contextlib import contextmanager
from .compat import StringIO, PY2, to_env, IS_TYPE_CHECKING
from .parser import parse_stream
from .compat import IS_TYPE_CHECKING, PY2, StringIO, to_env
from .parser import Binding, parse_stream
logger = logging.getLogger(__name__)
if IS_TYPE_CHECKING:
from typing import (
Dict, Iterator, Match, Optional, Pattern, Union, Text, IO, Tuple
Dict, Iterable, Iterator, Match, Optional, Pattern, Union, Text, IO, Tuple
)
if sys.version_info >= (3, 6):
_PathLike = os.PathLike
@@ -28,17 +30,39 @@ if IS_TYPE_CHECKING:
else:
_StringIO = StringIO[Text]
__posix_variable = re.compile(r'\$\{[^\}]*\}') # type: Pattern[Text]
__posix_variable = re.compile(
r"""
\$\{
(?P<name>[^\}:]*)
(?::-
(?P<default>[^\}]*)
)?
\}
""",
re.VERBOSE,
) # type: Pattern[Text]
def with_warn_for_invalid_lines(mappings):
# type: (Iterator[Binding]) -> Iterator[Binding]
for mapping in mappings:
if mapping.error:
logger.warning(
"Python-dotenv could not parse statement starting at line %s",
mapping.original.line,
)
yield mapping
class DotEnv():
def __init__(self, dotenv_path, verbose=False, encoding=None):
# type: (Union[Text, _PathLike, _StringIO], bool, Union[None, Text]) -> None
def __init__(self, dotenv_path, verbose=False, encoding=None, interpolate=True):
# type: (Union[Text, _PathLike, _StringIO], bool, Union[None, Text], bool) -> None
self.dotenv_path = dotenv_path # type: Union[Text,_PathLike, _StringIO]
self._dict = None # type: Optional[Dict[Text, Text]]
self._dict = None # type: Optional[Dict[Text, Optional[Text]]]
self.verbose = verbose # type: bool
self.encoding = encoding # type: Union[None, Text]
self.interpolate = interpolate # type: bool
@contextmanager
def _get_stream(self):
@@ -50,24 +74,28 @@ class DotEnv():
yield stream
else:
if self.verbose:
warnings.warn("File doesn't exist {}".format(self.dotenv_path)) # type: ignore
logger.info("Python-dotenv could not find configuration file %s.", self.dotenv_path or '.env')
yield StringIO('')
def dict(self):
# type: () -> Dict[Text, Text]
# type: () -> Dict[Text, Optional[Text]]
"""Return dotenv as dict"""
if self._dict:
return self._dict
values = OrderedDict(self.parse())
self._dict = resolve_nested_variables(values)
return self._dict
if self.interpolate:
values = resolve_nested_variables(self.parse())
else:
values = OrderedDict(self.parse())
self._dict = values
return values
def parse(self):
# type: () -> Iterator[Tuple[Text, Text]]
# type: () -> Iterator[Tuple[Text, Optional[Text]]]
with self._get_stream() as stream:
for mapping in parse_stream(stream):
if mapping.key is not None and mapping.value is not None:
for mapping in with_warn_for_invalid_lines(parse_stream(stream)):
if mapping.key is not None:
yield mapping.key, mapping.value
def set_as_environment_variables(self, override=False):
@@ -78,7 +106,8 @@ class DotEnv():
for k, v in self.dict().items():
if k in os.environ and not override:
continue
os.environ[to_env(k)] = to_env(v)
if v is not None:
os.environ[to_env(k)] = to_env(v)
return True
@@ -92,7 +121,7 @@ class DotEnv():
return data[key]
if self.verbose:
warnings.warn("key %s not found in %s." % (key, self.dotenv_path)) # type: ignore
logger.warning("Key %s not found in %s.", key, self.dotenv_path)
return None
@@ -111,6 +140,9 @@ def get_key(dotenv_path, key_to_get):
def rewrite(path):
# type: (_PathLike) -> Iterator[Tuple[IO[Text], IO[Text]]]
try:
if not os.path.isfile(path):
with io.open(path, "w+") as source:
source.write("")
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as dest:
with io.open(path) as source:
yield (source, dest) # type: ignore
@@ -122,8 +154,8 @@ def rewrite(path):
shutil.move(dest.name, path)
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
# type: (_PathLike, Text, Text, Text) -> Tuple[Optional[bool], Text, Text]
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always", export=False):
# type: (_PathLike, Text, Text, Text, bool) -> Tuple[Optional[bool], Text, Text]
"""
Adds or Updates a key/value to the given .env
@@ -131,24 +163,27 @@ def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
an orphan .env somewhere in the filesystem
"""
value_to_set = value_to_set.strip("'").strip('"')
if not os.path.exists(dotenv_path):
warnings.warn("can't write to %s - it doesn't exist." % dotenv_path) # type: ignore
return None, key_to_set, value_to_set
if " " in value_to_set:
quote_mode = "always"
line_template = '{}="{}"\n' if quote_mode == "always" else '{}={}\n'
line_out = line_template.format(key_to_set, value_to_set)
if quote_mode == "always":
value_out = '"{}"'.format(value_to_set.replace('"', '\\"'))
else:
value_out = value_to_set
if export:
line_out = 'export {}={}\n'.format(key_to_set, value_out)
else:
line_out = "{}={}\n".format(key_to_set, value_out)
with rewrite(dotenv_path) as (source, dest):
replaced = False
for mapping in parse_stream(source):
for mapping in with_warn_for_invalid_lines(parse_stream(source)):
if mapping.key == key_to_set:
dest.write(line_out)
replaced = True
else:
dest.write(mapping.original)
dest.write(mapping.original.string)
if not replaced:
dest.write(line_out)
@@ -164,48 +199,45 @@ def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
If the given key doesn't exist in the .env, fails
"""
if not os.path.exists(dotenv_path):
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path) # type: ignore
logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path)
return None, key_to_unset
removed = False
with rewrite(dotenv_path) as (source, dest):
for mapping in parse_stream(source):
for mapping in with_warn_for_invalid_lines(parse_stream(source)):
if mapping.key == key_to_unset:
removed = True
else:
dest.write(mapping.original)
dest.write(mapping.original.string)
if not removed:
warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path)) # type: ignore
logger.warning("Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path)
return None, key_to_unset
return removed, key_to_unset
def resolve_nested_variables(values):
# type: (Dict[Text, Text]) -> Dict[Text, Text]
def _replacement(name):
# type: (Text) -> Text
"""
get appropriate value for a variable name.
first search in environ, if not found,
then look into the dotenv variables
"""
ret = os.getenv(name, new_values.get(name, ""))
return ret
# type: (Iterable[Tuple[Text, Optional[Text]]]) -> Dict[Text, Optional[Text]]
def _replacement(name, default):
# type: (Text, Optional[Text]) -> Text
default = default if default is not None else ""
ret = new_values.get(name, os.getenv(name, default))
return ret # type: ignore
def _re_sub_callback(match_object):
def _re_sub_callback(match):
# type: (Match[Text]) -> Text
"""
From a match object gets the variable name and returns
the correct replacement
"""
return _replacement(match_object.group()[2:-1])
matches = match.groupdict()
return _replacement(name=matches["name"], default=matches["default"]) # type: ignore
new_values = {}
for k, v in values.items():
new_values[k] = __posix_variable.sub(_re_sub_callback, v)
for (k, v) in values:
new_values[k] = __posix_variable.sub(_re_sub_callback, v) if v is not None else None
return new_values
@@ -242,7 +274,7 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
main = __import__('__main__', None, None, fromlist=['__file__'])
return not hasattr(main, '__file__')
if usecwd or _is_interactive():
if usecwd or _is_interactive() or getattr(sys, 'frozen', False):
# Should work without __file__, e.g. in REPL or IPython notebook.
path = os.getcwd()
else:
@@ -257,6 +289,7 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
current_file = __file__
while frame.f_code.co_filename == current_file:
assert frame.f_back is not None
frame = frame.f_back
frame_filename = frame.f_code.co_filename
path = os.path.dirname(os.path.abspath(frame_filename))
@@ -272,8 +305,8 @@ def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
return ''
def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False, **kwargs):
# type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, bool, Union[None, Text]) -> bool
def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False, interpolate=True, **kwargs):
# type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, bool, bool, Union[None, Text]) -> bool
"""Parse a .env file and then load all the variables found as environment variables.
- *dotenv_path*: absolute or relative path to .env file.
@@ -283,10 +316,10 @@ def load_dotenv(dotenv_path=None, stream=None, verbose=False, override=False, **
Defaults to `False`.
"""
f = dotenv_path or stream or find_dotenv()
return DotEnv(f, verbose=verbose, **kwargs).set_as_environment_variables(override=override)
return DotEnv(f, verbose=verbose, interpolate=interpolate, **kwargs).set_as_environment_variables(override=override)
def dotenv_values(dotenv_path=None, stream=None, verbose=False, **kwargs):
# type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, Union[None, Text]) -> Dict[Text, Text]
def dotenv_values(dotenv_path=None, stream=None, verbose=False, interpolate=True, **kwargs):
# type: (Union[Text, _PathLike, None], Optional[_StringIO], bool, bool, Union[None, Text]) -> Dict[Text, Optional[Text]] # noqa: E501
f = dotenv_path or stream or find_dotenv()
return DotEnv(f, verbose=verbose, **kwargs).dict()
return DotEnv(f, verbose=verbose, interpolate=interpolate, **kwargs).dict()
+113 -45
View File
@@ -1,8 +1,7 @@
import codecs
import re
from .compat import to_text, IS_TYPE_CHECKING
from .compat import IS_TYPE_CHECKING, to_text
if IS_TYPE_CHECKING:
from typing import ( # noqa:F401
@@ -16,16 +15,18 @@ def make_regex(string, extra_flags=0):
return re.compile(to_text(string), re.UNICODE | extra_flags)
_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE)
_newline = make_regex(r"(\r\n|\n|\r)")
_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE)
_whitespace = make_regex(r"[^\S\r\n]*")
_export = make_regex(r"(?:export[^\S\r\n]+)?")
_single_quoted_key = make_regex(r"'([^']+)'")
_unquoted_key = make_regex(r"([^=\#\s]+)")
_equal_sign = make_regex(r"[^\S\r\n]*=[^\S\r\n]*")
_equal_sign = make_regex(r"(=[^\S\r\n]*)")
_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'")
_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"')
_unquoted_value_part = make_regex(r"([^ \r\n]*)")
_comment = make_regex(r"(?:\s*#[^\r\n]*)?")
_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r)?")
_unquoted_value = make_regex(r"([^\r\n]*)")
_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?")
_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)")
_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?")
_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]")
_single_quote_escapes = make_regex(r"\\[\\']")
@@ -36,14 +37,64 @@ try:
# when we are type checking, and the linter is upset if we
# re-import
import typing
Binding = typing.NamedTuple("Binding", [("key", typing.Optional[typing.Text]),
("value", typing.Optional[typing.Text]),
("original", typing.Text)])
except ImportError: # pragma: no cover
Original = typing.NamedTuple(
"Original",
[
("string", typing.Text),
("line", int),
],
)
Binding = typing.NamedTuple(
"Binding",
[
("key", typing.Optional[typing.Text]),
("value", typing.Optional[typing.Text]),
("original", Original),
("error", bool),
],
)
except (ImportError, AttributeError):
from collections import namedtuple
Binding = namedtuple("Binding", ["key", # type: ignore
"value",
"original"]) # type: Tuple[Optional[Text], Optional[Text], Text]
Original = namedtuple( # type: ignore
"Original",
[
"string",
"line",
],
)
Binding = namedtuple( # type: ignore
"Binding",
[
"key",
"value",
"original",
"error",
],
)
class Position:
def __init__(self, chars, line):
# type: (int, int) -> None
self.chars = chars
self.line = line
@classmethod
def start(cls):
# type: () -> Position
return cls(chars=0, line=1)
def set(self, other):
# type: (Position) -> None
self.chars = other.chars
self.line = other.line
def advance(self, string):
# type: (Text) -> None
self.chars += len(string)
self.line += len(re.findall(_newline, string))
class Error(Exception):
@@ -54,39 +105,42 @@ class Reader:
def __init__(self, stream):
# type: (IO[Text]) -> None
self.string = stream.read()
self.position = 0
self.mark = 0
self.position = Position.start()
self.mark = Position.start()
def has_next(self):
# type: () -> bool
return self.position < len(self.string)
return self.position.chars < len(self.string)
def set_mark(self):
# type: () -> None
self.mark = self.position
self.mark.set(self.position)
def get_marked(self):
# type: () -> Text
return self.string[self.mark:self.position]
# type: () -> Original
return Original(
string=self.string[self.mark.chars:self.position.chars],
line=self.mark.line,
)
def peek(self, count):
# type: (int) -> Text
return self.string[self.position:self.position + count]
return self.string[self.position.chars:self.position.chars + count]
def read(self, count):
# type: (int) -> Text
result = self.string[self.position:self.position + count]
result = self.string[self.position.chars:self.position.chars + count]
if len(result) < count:
raise Error("read: End of string")
self.position += count
self.position.advance(result)
return result
def read_regex(self, regex):
# type: (Pattern[Text]) -> Sequence[Text]
match = regex.match(self.string, self.position)
match = regex.match(self.string, self.position.chars)
if match is None:
raise Error("read_regex: Pattern not found")
self.position = match.end()
self.position.advance(self.string[match.start():match.end()])
return match.groups()
@@ -100,9 +154,11 @@ def decode_escapes(regex, string):
def parse_key(reader):
# type: (Reader) -> Text
# type: (Reader) -> Optional[Text]
char = reader.peek(1)
if char == "'":
if char == "#":
return None
elif char == "'":
(key,) = reader.read_regex(_single_quoted_key)
else:
(key,) = reader.read_regex(_unquoted_key)
@@ -111,14 +167,8 @@ def parse_key(reader):
def parse_unquoted_value(reader):
# type: (Reader) -> Text
value = u""
while True:
(part,) = reader.read_regex(_unquoted_value_part)
value += part
after = reader.peek(2)
if len(after) < 2 or after[0] in u"\r\n" or after[1] in u" #\r\n":
return value
value += reader.read(2)
(part,) = reader.read_regex(_unquoted_value)
return re.sub(r"\s+#.*", "", part).rstrip()
def parse_value(reader):
@@ -140,24 +190,42 @@ def parse_binding(reader):
# type: (Reader) -> Binding
reader.set_mark()
try:
reader.read_regex(_whitespace)
reader.read_regex(_multiline_whitespace)
if not reader.has_next():
return Binding(
key=None,
value=None,
original=reader.get_marked(),
error=False,
)
reader.read_regex(_export)
key = parse_key(reader)
reader.read_regex(_equal_sign)
value = parse_value(reader)
reader.read_regex(_whitespace)
if reader.peek(1) == "=":
reader.read_regex(_equal_sign)
value = parse_value(reader) # type: Optional[Text]
else:
value = None
reader.read_regex(_comment)
reader.read_regex(_end_of_line)
return Binding(key=key, value=value, original=reader.get_marked())
return Binding(
key=key,
value=value,
original=reader.get_marked(),
error=False,
)
except Error:
reader.read_regex(_rest_of_line)
return Binding(key=None, value=None, original=reader.get_marked())
return Binding(
key=None,
value=None,
original=reader.get_marked(),
error=True,
)
def parse_stream(stream):
# type:(IO[Text]) -> Iterator[Binding]
# type: (IO[Text]) -> Iterator[Binding]
reader = Reader(stream)
while reader.has_next():
try:
yield parse_binding(reader)
except Error:
return
yield parse_binding(reader)
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.10.3"
__version__ = "0.15.0"
+2 -2
View File
@@ -33,10 +33,10 @@ match in a more advanced way.
"""
__title__ = 'first'
__version__ = '2.0.1'
__version__ = '2.0.2'
__author__ = 'Hynek Schlawack'
__license__ = 'MIT'
__copyright__ = 'Copyright 20122013 Hynek Schlawack'
__copyright__ = 'Copyright 2012 Hynek Schlawack'
def first(iterable, default=None, key=None):
+2
View File
@@ -300,6 +300,8 @@ def ulabel(label):
label = label.lower()
if label.startswith(_alabel_prefix):
label = label[len(_alabel_prefix):]
if not label:
raise IDNAError('Malformed A-label, no Punycode eligible content found')
if label.decode('ascii')[-1] == '-':
raise IDNAError('A-label must not end with a hyphen')
else:
+83 -24
View File
@@ -1,6 +1,6 @@
# This file is automatically generated by tools/idna-data
__version__ = "12.1.0"
__version__ = "13.0.0"
scripts = {
'Greek': (
0x37000000374,
@@ -48,16 +48,18 @@ scripts = {
0x300700003008,
0x30210000302a,
0x30380000303c,
0x340000004db6,
0x4e0000009ff0,
0x340000004dc0,
0x4e0000009ffd,
0xf9000000fa6e,
0xfa700000fada,
0x200000002a6d7,
0x16ff000016ff2,
0x200000002a6de,
0x2a7000002b735,
0x2b7400002b81e,
0x2b8200002cea2,
0x2ceb00002ebe1,
0x2f8000002fa1e,
0x300000003134b,
),
'Hebrew': (
0x591000005c8,
@@ -389,9 +391,9 @@ joining_types = {
0x853: 68,
0x854: 82,
0x855: 68,
0x856: 85,
0x857: 85,
0x858: 85,
0x856: 82,
0x857: 82,
0x858: 82,
0x860: 68,
0x861: 85,
0x862: 68,
@@ -432,6 +434,16 @@ joining_types = {
0x8bb: 68,
0x8bc: 68,
0x8bd: 68,
0x8be: 68,
0x8bf: 68,
0x8c0: 68,
0x8c1: 68,
0x8c2: 68,
0x8c3: 68,
0x8c4: 68,
0x8c5: 68,
0x8c6: 68,
0x8c7: 68,
0x8e2: 85,
0x1806: 85,
0x1807: 68,
@@ -756,6 +768,34 @@ joining_types = {
0x10f52: 68,
0x10f53: 68,
0x10f54: 82,
0x10fb0: 68,
0x10fb1: 85,
0x10fb2: 68,
0x10fb3: 68,
0x10fb4: 82,
0x10fb5: 82,
0x10fb6: 82,
0x10fb7: 85,
0x10fb8: 68,
0x10fb9: 82,
0x10fba: 82,
0x10fbb: 68,
0x10fbc: 68,
0x10fbd: 82,
0x10fbe: 68,
0x10fbf: 68,
0x10fc0: 85,
0x10fc1: 68,
0x10fc2: 82,
0x10fc3: 82,
0x10fc4: 68,
0x10fc5: 85,
0x10fc6: 85,
0x10fc7: 85,
0x10fc8: 85,
0x10fc9: 82,
0x10fca: 68,
0x10fcb: 76,
0x110bd: 85,
0x110cd: 85,
0x1e900: 68,
@@ -1129,7 +1169,7 @@ codepoint_classes = {
0x8400000085c,
0x8600000086b,
0x8a0000008b5,
0x8b6000008be,
0x8b6000008c8,
0x8d3000008e2,
0x8e300000958,
0x96000000964,
@@ -1188,7 +1228,7 @@ codepoint_classes = {
0xb3c00000b45,
0xb4700000b49,
0xb4b00000b4e,
0xb5600000b58,
0xb5500000b58,
0xb5f00000b64,
0xb6600000b70,
0xb7100000b72,
@@ -1233,8 +1273,7 @@ codepoint_classes = {
0xce000000ce4,
0xce600000cf0,
0xcf100000cf3,
0xd0000000d04,
0xd0500000d0d,
0xd0000000d0d,
0xd0e00000d11,
0xd1200000d45,
0xd4600000d49,
@@ -1243,7 +1282,7 @@ codepoint_classes = {
0xd5f00000d64,
0xd6600000d70,
0xd7a00000d80,
0xd8200000d84,
0xd8100000d84,
0xd8500000d97,
0xd9a00000db2,
0xdb300000dbc,
@@ -1358,6 +1397,7 @@ codepoint_classes = {
0x1a9000001a9a,
0x1aa700001aa8,
0x1ab000001abe,
0x1abf00001ac1,
0x1b0000001b4c,
0x1b5000001b5a,
0x1b6b00001b74,
@@ -1609,10 +1649,10 @@ codepoint_classes = {
0x30a1000030fb,
0x30fc000030ff,
0x310500003130,
0x31a0000031bb,
0x31a0000031c0,
0x31f000003200,
0x340000004db6,
0x4e0000009ff0,
0x340000004dc0,
0x4e0000009ffd,
0xa0000000a48d,
0xa4d00000a4fe,
0xa5000000a60d,
@@ -1727,8 +1767,11 @@ codepoint_classes = {
0xa7bd0000a7be,
0xa7bf0000a7c0,
0xa7c30000a7c4,
0xa7f70000a7f8,
0xa7c80000a7c9,
0xa7ca0000a7cb,
0xa7f60000a7f8,
0xa7fa0000a828,
0xa82c0000a82d,
0xa8400000a874,
0xa8800000a8c6,
0xa8d00000a8da,
@@ -1753,7 +1796,7 @@ codepoint_classes = {
0xab200000ab27,
0xab280000ab2f,
0xab300000ab5b,
0xab600000ab68,
0xab600000ab6a,
0xabc00000abeb,
0xabec0000abee,
0xabf00000abfa,
@@ -1827,9 +1870,13 @@ codepoint_classes = {
0x10cc000010cf3,
0x10d0000010d28,
0x10d3000010d3a,
0x10e8000010eaa,
0x10eab00010ead,
0x10eb000010eb2,
0x10f0000010f1d,
0x10f2700010f28,
0x10f3000010f51,
0x10fb000010fc5,
0x10fe000010ff7,
0x1100000011047,
0x1106600011070,
@@ -1838,12 +1885,12 @@ codepoint_classes = {
0x110f0000110fa,
0x1110000011135,
0x1113600011140,
0x1114400011147,
0x1114400011148,
0x1115000011174,
0x1117600011177,
0x11180000111c5,
0x111c9000111cd,
0x111d0000111db,
0x111ce000111db,
0x111dc000111dd,
0x1120000011212,
0x1121300011238,
@@ -1872,7 +1919,7 @@ codepoint_classes = {
0x1137000011375,
0x114000001144b,
0x114500001145a,
0x1145e00011460,
0x1145e00011462,
0x11480000114c6,
0x114c7000114c8,
0x114d0000114da,
@@ -1889,7 +1936,14 @@ codepoint_classes = {
0x117300001173a,
0x118000001183b,
0x118c0000118ea,
0x118ff00011900,
0x118ff00011907,
0x119090001190a,
0x1190c00011914,
0x1191500011917,
0x1191800011936,
0x1193700011939,
0x1193b00011944,
0x119500001195a,
0x119a0000119a8,
0x119aa000119d8,
0x119da000119e2,
@@ -1920,6 +1974,7 @@ codepoint_classes = {
0x11d9300011d99,
0x11da000011daa,
0x11ee000011ef7,
0x11fb000011fb1,
0x120000001239a,
0x1248000012544,
0x130000001342f,
@@ -1939,9 +1994,11 @@ codepoint_classes = {
0x16f4f00016f88,
0x16f8f00016fa0,
0x16fe000016fe2,
0x16fe300016fe4,
0x16fe300016fe5,
0x16ff000016ff2,
0x17000000187f8,
0x1880000018af3,
0x1880000018cd6,
0x18d0000018d09,
0x1b0000001b11f,
0x1b1500001b153,
0x1b1640001b168,
@@ -1971,11 +2028,13 @@ codepoint_classes = {
0x1e8d00001e8d7,
0x1e9220001e94c,
0x1e9500001e95a,
0x200000002a6d7,
0x1fbf00001fbfa,
0x200000002a6de,
0x2a7000002b735,
0x2b7400002b81e,
0x2b8200002cea2,
0x2ceb00002ebe1,
0x300000003134b,
),
'CONTEXTJ': (
0x200c0000200e,
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = '2.9'
__version__ = '2.10'
+266 -226
View File
File diff suppressed because it is too large Load Diff
+24 -6
View File
@@ -29,6 +29,7 @@ from ._compat import (
email_message_from_string,
PyPy_repr,
unique_ordered,
str,
)
from importlib import import_module
from itertools import starmap
@@ -54,6 +55,15 @@ __all__ = [
class PackageNotFoundError(ModuleNotFoundError):
"""The package was not found."""
def __str__(self):
tmpl = "No package metadata was found for {self.name}"
return tmpl.format(**locals())
@property
def name(self):
name, = self.args
return name
class EntryPoint(
PyPy_repr,
@@ -198,7 +208,7 @@ class Distribution:
"""
for resolver in cls._discover_resolvers():
dists = resolver(DistributionFinder.Context(name=name))
dist = next(dists, None)
dist = next(iter(dists), None)
if dist is not None:
return dist
else:
@@ -241,6 +251,17 @@ class Distribution:
)
return filter(None, declared)
@classmethod
def _local(cls, root='.'):
from pep517 import build, meta
system = build.compat_system(root)
builder = functools.partial(
meta.build,
source_dir=root,
system=system,
)
return PathDistribution(zipp.Path(meta.build_as_zip(builder)))
@property
def metadata(self):
"""Return the parsed metadata for this Distribution.
@@ -418,8 +439,8 @@ class FastPath:
"""
def __init__(self, root):
self.root = root
self.base = os.path.basename(root).lower()
self.root = str(root)
self.base = os.path.basename(self.root).lower()
def joinpath(self, child):
return pathlib.Path(self.root, child)
@@ -597,6 +618,3 @@ def requires(distribution_name):
packaging.requirement.Requirement.
"""
return distribution(distribution_name).requires
__version__ = version(__name__)
+3 -1
View File
@@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals
import io
import abc
@@ -26,6 +26,8 @@ else: # pragma: nocover
NotADirectoryError = IOError, OSError
PermissionError = IOError, OSError
str = type('')
suppress = contextlib.suppress
if sys.version_info > (3, 5): # pragma: nocover
+32
View File
@@ -2,6 +2,38 @@
importlib_metadata NEWS
=========================
v2.0.0
======
* ``importlib_metadata`` no longer presents a
``__version__`` attribute. Consumers wishing to
resolve the version of the package should query it
directly with
``importlib_metadata.version('importlib-metadata')``.
Closes #71.
v1.7.0
======
* ``PathNotFoundError`` now has a custom ``__str__``
mentioning "package metadata" being missing to help
guide users to the cause when the package is installed
but no metadata is present. Closes #124.
v1.6.1
======
* Added ``Distribution._local()`` as a provisional
demonstration of how to load metadata for a local
package. Implicitly requires that
`pep517 <https://pypi.org/project/pep517>`_ is
installed. Ref #42.
* Ensure inputs to FastPath are Unicode. Closes #121.
* Tests now rely on ``importlib.resources.files`` (and
backport) instead of the older ``path`` function.
* Support any iterable from ``find_distributions``.
Closes #122.
v1.6.0
======
+2 -2
View File
@@ -95,7 +95,7 @@ The ``group`` and ``name`` are arbitrary values defined by the package author
and usually a client will wish to resolve all entry points for a particular
group. Read `the setuptools docs
<https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins>`_
for more information on entrypoints, their definition, and usage.
for more information on entry points, their definition, and usage.
.. _metadata:
@@ -236,7 +236,7 @@ method::
"""
The ``DistributionFinder.Context`` object provides ``.path`` and ``.name``
properties indicating the path to search and names to match and may
properties indicating the path to search and name to match and may
supply other relevant context.
What this means in practice is that to support finding distribution package
+22
View File
@@ -5,6 +5,7 @@ import sys
import shutil
import tempfile
import textwrap
import test.support
from .._compat import pathlib, contextlib
@@ -166,6 +167,21 @@ class EggInfoFile(OnSysPath, SiteDir):
build_files(EggInfoFile.files, prefix=self.site_dir)
class LocalPackage:
files = {
"setup.py": """
import setuptools
setuptools.setup(name="local-pkg", version="2.0.1")
""",
}
def setUp(self):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
self.fixtures.enter_context(tempdir_as_cwd())
build_files(self.files)
def build_files(file_defs, prefix=pathlib.Path()):
"""Build a set of files/directories, as described by the
@@ -200,6 +216,12 @@ def build_files(file_defs, prefix=pathlib.Path()):
f.write(DALS(contents))
class FileBuilder:
def unicode_filename(self):
return test.support.FS_NONASCII or \
self.skip("File system does not support non-ascii.")
def DALS(str):
"Dedent and left-strip"
return textwrap.dedent(str).lstrip()
+3 -2
View File
@@ -4,7 +4,7 @@ import unittest
from . import fixtures
from .. import (
Distribution, PackageNotFoundError, __version__, distribution,
Distribution, PackageNotFoundError, distribution,
entry_points, files, metadata, requires, version,
)
@@ -68,7 +68,8 @@ class APITests(
assert 'Topic :: Software Development :: Libraries' in classifiers
def test_importlib_metadata_version(self):
assert re.match(self.version_pattern, __version__)
resolved = version('importlib-metadata')
assert re.match(self.version_pattern, resolved)
@staticmethod
def _test_files(files):
@@ -1,9 +1,14 @@
# coding: utf-8
from __future__ import unicode_literals
import unittest
import packaging.requirements
import packaging.version
from . import fixtures
from .. import (
Distribution,
_compat,
version,
)
@@ -40,3 +45,10 @@ class FinderTests(fixtures.Fixtures, unittest.TestCase):
self.fixtures.enter_context(
fixtures.install_finder(ModuleFreeFinder()))
_compat.disable_stdlib_finder()
class LocalProjectTests(fixtures.LocalPackage, unittest.TestCase):
def test_find_local(self):
dist = Distribution._local()
assert dist.metadata['Name'] == 'local-pkg'
assert dist.version == '2.0.1'
+27
View File
@@ -35,6 +35,18 @@ class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
with self.assertRaises(PackageNotFoundError):
Distribution.from_name('does-not-exist')
def test_package_not_found_mentions_metadata(self):
"""
When a package is not found, that could indicate that the
packgae is not installed or that it is installed without
metadata. Ensure the exception mentions metadata to help
guide users toward the cause. See #124.
"""
with self.assertRaises(PackageNotFoundError) as ctx:
Distribution.from_name('does-not-exist')
assert "metadata" in str(ctx.exception)
def test_new_style_classes(self):
self.assertIsInstance(Distribution, type)
self.assertIsInstance(MetadataPathFinder, type)
@@ -256,3 +268,18 @@ class TestEntryPoints(unittest.TestCase):
def test_attr(self):
assert self.ep.attr is None
class FileSystem(
fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder,
unittest.TestCase):
def test_unicode_dir_on_sys_path(self):
"""
Ensure a Unicode subdirectory of a directory on sys.path
does not crash.
"""
fixtures.build_files(
{self.unicode_filename(): {}},
prefix=self.site_dir,
)
list(distributions())
+14 -11
View File
@@ -7,9 +7,11 @@ from .. import (
)
try:
from importlib.resources import path
except ImportError:
from importlib_resources import path
from importlib import resources
getattr(resources, 'files')
getattr(resources, 'as_file')
except (ImportError, AttributeError):
import importlib_resources as resources
try:
from contextlib import ExitStack
@@ -20,15 +22,19 @@ except ImportError:
class TestZip(unittest.TestCase):
root = 'importlib_metadata.tests.data'
def _fixture_on_path(self, filename):
pkg_file = resources.files(self.root).joinpath(filename)
file = self.resources.enter_context(resources.as_file(pkg_file))
assert file.name.startswith('example-'), file.name
sys.path.insert(0, str(file))
self.resources.callback(sys.path.pop, 0)
def setUp(self):
# Find the path to the example-*.whl so we can add it to the front of
# sys.path, where we'll then try to find the metadata thereof.
self.resources = ExitStack()
self.addCleanup(self.resources.close)
wheel = self.resources.enter_context(
path(self.root, 'example-21.12-py3-none-any.whl'))
sys.path.insert(0, str(wheel))
self.resources.callback(sys.path.pop, 0)
self._fixture_on_path('example-21.12-py3-none-any.whl')
def test_zip_version(self):
self.assertEqual(version('example'), '21.12')
@@ -66,10 +72,7 @@ class TestEgg(TestZip):
# sys.path, where we'll then try to find the metadata thereof.
self.resources = ExitStack()
self.addCleanup(self.resources.close)
egg = self.resources.enter_context(
path(self.root, 'example-21.12-py3.6.egg'))
sys.path.insert(0, str(egg))
self.resources.callback(sys.path.pop, 0)
self._fixture_on_path('example-21.12-py3.6.egg')
def test_files(self):
for file in files('example'):
+5 -8
View File
@@ -2,10 +2,12 @@
import sys
from ._compat import metadata
from ._common import as_file
from ._common import (
as_file, files,
)
# for compatibility. Ref #88
# For compatibility. Ref #88.
# Also requires hook-importlib_resources.py (Ref #101).
__import__('importlib_resources.trees')
@@ -30,7 +32,6 @@ if sys.version_info >= (3,):
Package,
Resource,
contents,
files,
is_resource,
open_binary,
open_text,
@@ -42,7 +43,6 @@ if sys.version_info >= (3,):
else:
from importlib_resources._py2 import (
contents,
files,
is_resource,
open_binary,
open_text,
@@ -51,6 +51,3 @@ else:
read_text,
)
del __all__[:3]
__version__ = metadata.version('importlib_resources')
+70 -26
View File
@@ -3,12 +3,76 @@ from __future__ import absolute_import
import os
import tempfile
import contextlib
import types
import importlib
from ._compat import (
Path, package_spec, FileNotFoundError, ZipPath,
singledispatch, suppress,
Path, FileNotFoundError,
singledispatch, package_spec,
)
if False: # TYPE_CHECKING
from typing import Union, Any, Optional
from .abc import ResourceReader
Package = Union[types.ModuleType, str]
def files(package):
"""
Get a Traversable resource from a package
"""
return from_package(get_package(package))
def normalize_path(path):
# type: (Any) -> str
"""Normalize a path by ensuring it is a string.
If the resulting string contains path separators, an exception is raised.
"""
str_path = str(path)
parent, file_name = os.path.split(str_path)
if parent:
raise ValueError('{!r} must be only a file name'.format(path))
return file_name
def get_resource_reader(package):
# type: (types.ModuleType) -> Optional[ResourceReader]
"""
Return the package's loader if it's a ResourceReader.
"""
# We can't use
# a issubclass() check here because apparently abc.'s __subclasscheck__()
# hook wants to create a weak reference to the object, but
# zipimport.zipimporter does not support weak references, resulting in a
# TypeError. That seems terrible.
spec = package.__spec__
reader = getattr(spec.loader, 'get_resource_reader', None)
if reader is None:
return None
return reader(spec.name)
def resolve(cand):
# type: (Package) -> types.ModuleType
return (
cand if isinstance(cand, types.ModuleType)
else importlib.import_module(cand)
)
def get_package(package):
# type: (Package) -> types.ModuleType
"""Take a package name or module object and return the module.
Raise an exception if the resolved module is not a package.
"""
resolved = resolve(package)
if package_spec(resolved).submodule_search_locations is None:
raise TypeError('{!r} is not a package'.format(package))
return resolved
def from_package(package):
"""
@@ -16,27 +80,8 @@ def from_package(package):
"""
spec = package_spec(package)
return from_traversable_resources(spec) or fallback_resources(spec)
def from_traversable_resources(spec):
"""
If the spec.loader implements TraversableResources,
directly or implicitly, it will have a ``files()`` method.
"""
with suppress(AttributeError):
return spec.loader.files()
def fallback_resources(spec):
package_directory = Path(spec.origin).parent
try:
archive_path = spec.loader.archive
rel_path = package_directory.relative_to(archive_path)
return ZipPath(archive_path, str(rel_path) + '/')
except Exception:
pass
return package_directory
reader = spec.loader.get_resource_reader(spec.name)
return reader.files()
@contextlib.contextmanager
@@ -48,6 +93,7 @@ def _tempfile(reader, suffix=''):
try:
os.write(fd, reader())
os.close(fd)
del reader
yield Path(raw_path)
finally:
try:
@@ -57,14 +103,12 @@ def _tempfile(reader, suffix=''):
@singledispatch
@contextlib.contextmanager
def as_file(path):
"""
Given a Traversable object, return that object as a
path on the local file system in a context manager.
"""
with _tempfile(path.read_bytes, suffix=path.name) as local:
yield local
return _tempfile(path.read_bytes, suffix=path.name)
@as_file.register(Path)
+81 -15
View File
@@ -1,16 +1,17 @@
from __future__ import absolute_import
import sys
# flake8: noqa
try:
if sys.version_info > (3,5):
from pathlib import Path, PurePath
except ImportError:
else:
from pathlib2 import Path, PurePath # type: ignore
try:
if sys.version_info > (3,):
from contextlib import suppress
except ImportError:
else:
from contextlib2 import suppress # type: ignore
@@ -36,9 +37,9 @@ except NameError:
try:
from importlib import metadata
except ImportError:
import importlib_metadata as metadata # type: ignore
NotADirectoryError = NotADirectoryError # type: ignore
except NameError:
NotADirectoryError = OSError # type: ignore
try:
@@ -60,14 +61,79 @@ except ImportError:
Protocol = ABC # type: ignore
class PackageSpec(object):
def __init__(self, **kwargs):
vars(self).update(kwargs)
__metaclass__ = type
class PackageSpec:
def __init__(self, **kwargs):
vars(self).update(kwargs)
class TraversableResourcesAdapter:
def __init__(self, spec):
self.spec = spec
self.loader = LoaderAdapter(spec)
def __getattr__(self, name):
return getattr(self.spec, name)
class LoaderAdapter:
"""
Adapt loaders to provide TraversableResources and other
compatibility.
"""
def __init__(self, spec):
self.spec = spec
@property
def path(self):
# Python < 3
return self.spec.origin
def get_resource_reader(self, name):
# Python < 3.9
from . import readers
def _zip_reader(spec):
with suppress(AttributeError):
return readers.ZipReader(spec.loader, spec.name)
def _namespace_reader(spec):
with suppress(AttributeError, ValueError):
return readers.NamespaceReader(spec.submodule_search_locations)
def _available_reader(spec):
with suppress(AttributeError):
return spec.loader.get_resource_reader(spec.name)
def _native_reader(spec):
reader = _available_reader(spec)
return reader if hasattr(reader, 'files') else None
return (
# native reader if it supplies 'files'
_native_reader(self.spec) or
# local ZipReader if a zip module
_zip_reader(self.spec) or
# local NamespaceReader if a namespace module
_namespace_reader(self.spec) or
# local FileReader
readers.FileReader(self)
)
def package_spec(package):
return getattr(package, '__spec__', None) or \
PackageSpec(
origin=package.__file__,
loader=getattr(package, '__loader__', None),
)
"""
Construct a minimal package spec suitable for
matching the interfaces this library relies upon
in later Python versions.
"""
spec = getattr(package, '__spec__', None) or \
PackageSpec(
origin=package.__file__,
loader=getattr(package, '__loader__', None),
name=package.__name__,
submodule_search_locations=getattr(package, '__path__', None),
)
return TraversableResourcesAdapter(spec)
+6 -41
View File
@@ -3,44 +3,13 @@ import errno
from . import _common
from ._compat import FileNotFoundError
from importlib import import_module
from io import BytesIO, TextIOWrapper, open as io_open
def _resolve(name):
"""If name is a string, resolve to a module."""
if not isinstance(name, basestring): # noqa: F821
return name
return import_module(name)
def _get_package(package):
"""Normalize a path by ensuring it is a string.
If the resulting string contains path separators, an exception is raised.
"""
module = _resolve(package)
if not hasattr(module, '__path__'):
raise TypeError("{!r} is not a package".format(package))
return module
def _normalize_path(path):
"""Normalize a path by ensuring it is a string.
If the resulting string contains path separators, an exception is raised.
"""
str_path = str(path)
parent, file_name = os.path.split(str_path)
if parent:
raise ValueError("{!r} must be only a file name".format(path))
return file_name
def open_binary(package, resource):
"""Return a file-like object opened for binary reading of the resource."""
resource = _normalize_path(resource)
package = _get_package(package)
resource = _common.normalize_path(resource)
package = _common.get_package(package)
# Using pathlib doesn't work well here due to the lack of 'strict' argument
# for pathlib.Path.resolve() prior to Python 3.6.
package_path = os.path.dirname(package.__file__)
@@ -89,10 +58,6 @@ def read_text(package, resource, encoding='utf-8', errors='strict'):
return fp.read()
def files(package):
return _common.from_package(_get_package(package))
def path(package, resource):
"""A context manager providing a file path object to the resource.
@@ -102,7 +67,7 @@ def path(package, resource):
raised if the file was deleted prior to the context manager
exiting).
"""
path = files(package).joinpath(_normalize_path(resource))
path = _common.files(package).joinpath(_common.normalize_path(resource))
if not path.is_file():
raise FileNotFoundError(path)
return _common.as_file(path)
@@ -113,8 +78,8 @@ def is_resource(package, name):
Directories are *not* resources.
"""
package = _get_package(package)
_normalize_path(name)
package = _common.get_package(package)
_common.normalize_path(name)
try:
package_contents = set(contents(package))
except OSError as error:
@@ -138,5 +103,5 @@ def contents(package):
not considered resources. Use `is_resource()` on each entry returned here
to check if it is a resource or not.
"""
package = _get_package(package)
package = _common.get_package(package)
return list(item.name for item in _common.from_package(package).iterdir())
+66 -109
View File
@@ -1,10 +1,8 @@
import os
import sys
import io
from . import abc as resources_abc
from . import _common
from contextlib import contextmanager, suppress
from importlib import import_module
from contextlib import suppress
from importlib.abc import ResourceLoader
from io import BytesIO, TextIOWrapper
from pathlib import Path
@@ -12,92 +10,48 @@ from types import ModuleType
from typing import Iterable, Iterator, Optional, Set, Union # noqa: F401
from typing import cast
from typing.io import BinaryIO, TextIO
from collections.abc import Sequence
from functools import singledispatch
if False: # TYPE_CHECKING
from typing import ContextManager
Package = Union[ModuleType, str]
if sys.version_info >= (3, 6):
Resource = Union[str, os.PathLike] # pragma: <=35
else:
Resource = str # pragma: >=36
def _resolve(name) -> ModuleType:
"""If name is a string, resolve to a module."""
if hasattr(name, '__spec__'):
return name
return import_module(name)
def _get_package(package) -> ModuleType:
"""Take a package name or module object and return the module.
If a name, the module is imported. If the resolved module
object is not a package, raise an exception.
"""
module = _resolve(package)
if module.__spec__.submodule_search_locations is None:
raise TypeError('{!r} is not a package'.format(package))
return module
def _normalize_path(path) -> str:
"""Normalize a path by ensuring it is a string.
If the resulting string contains path separators, an exception is raised.
"""
str_path = str(path)
parent, file_name = os.path.split(str_path)
if parent:
raise ValueError('{!r} must be only a file name'.format(path))
return file_name
def _get_resource_reader(
package: ModuleType) -> Optional[resources_abc.ResourceReader]:
# Return the package's loader if it's a ResourceReader. We can't use
# a issubclass() check here because apparently abc.'s __subclasscheck__()
# hook wants to create a weak reference to the object, but
# zipimport.zipimporter does not support weak references, resulting in a
# TypeError. That seems terrible.
spec = package.__spec__
reader = getattr(spec.loader, 'get_resource_reader', None)
if reader is None:
return None
return cast(resources_abc.ResourceReader, reader(spec.name))
Resource = Union[str, os.PathLike]
def open_binary(package: Package, resource: Resource) -> BinaryIO:
"""Return a file-like object opened for binary reading of the resource."""
resource = _normalize_path(resource)
package = _get_package(package)
reader = _get_resource_reader(package)
resource = _common.normalize_path(resource)
package = _common.get_package(package)
reader = _common.get_resource_reader(package)
if reader is not None:
return reader.open_resource(resource)
# Using pathlib doesn't work well here due to the lack of 'strict'
# argument for pathlib.Path.resolve() prior to Python 3.6.
absolute_package_path = os.path.abspath(
package.__spec__.origin or 'non-existent file')
package_path = os.path.dirname(absolute_package_path)
full_path = os.path.join(package_path, resource)
try:
return open(full_path, mode='rb')
except OSError:
# Just assume the loader is a resource loader; all the relevant
# importlib.machinery loaders are and an AttributeError for
# get_data() will make it clear what is needed from the loader.
loader = cast(ResourceLoader, package.__spec__.loader)
data = None
if hasattr(package.__spec__.loader, 'get_data'):
with suppress(OSError):
data = loader.get_data(full_path)
if data is None:
package_name = package.__spec__.name
message = '{!r} resource not found in {!r}'.format(
resource, package_name)
raise FileNotFoundError(message)
return BytesIO(data)
if package.__spec__.submodule_search_locations is not None:
paths = package.__spec__.submodule_search_locations
elif package.__spec__.origin is not None:
paths = [os.path.dirname(os.path.abspath(package.__spec__.origin))]
for package_path in paths:
full_path = os.path.join(package_path, resource)
try:
return open(full_path, mode='rb')
except OSError:
# Just assume the loader is a resource loader; all the relevant
# importlib.machinery loaders are and an AttributeError for
# get_data() will make it clear what is needed from the loader.
loader = cast(ResourceLoader, package.__spec__.loader)
data = None
if hasattr(package.__spec__.loader, 'get_data'):
with suppress(OSError):
data = loader.get_data(full_path)
if data is not None:
return BytesIO(data)
raise FileNotFoundError('{!r} resource not found in {!r}'.format(
resource, package.__spec__.name))
def open_text(package: Package,
@@ -128,13 +82,6 @@ def read_text(package: Package,
return fp.read()
def files(package: Package) -> resources_abc.Traversable:
"""
Get a Traversable resource from a package
"""
return _common.from_package(_get_package(package))
def path(
package: Package, resource: Resource,
) -> 'ContextManager[Path]':
@@ -146,23 +93,28 @@ def path(
raised if the file was deleted prior to the context manager
exiting).
"""
reader = _get_resource_reader(_get_package(package))
reader = _common.get_resource_reader(_common.get_package(package))
return (
_path_from_reader(reader, resource)
_path_from_reader(reader, _common.normalize_path(resource))
if reader else
_common.as_file(files(package).joinpath(_normalize_path(resource)))
_common.as_file(
_common.files(package).joinpath(_common.normalize_path(resource)))
)
@contextmanager
def _path_from_reader(reader, resource):
norm_resource = _normalize_path(resource)
return _path_from_resource_path(reader, resource) or \
_path_from_open_resource(reader, resource)
def _path_from_resource_path(reader, resource):
with suppress(FileNotFoundError):
yield Path(reader.resource_path(norm_resource))
return
opener_reader = reader.open_resource(norm_resource)
with _common._tempfile(opener_reader.read, suffix=norm_resource) as res:
yield res
return Path(reader.resource_path(resource))
def _path_from_open_resource(reader, resource):
saved = io.BytesIO(reader.open_resource(resource).read())
return _common._tempfile(saved.read, suffix=resource)
def is_resource(package: Package, name: str) -> bool:
@@ -170,9 +122,9 @@ def is_resource(package: Package, name: str) -> bool:
Directories are *not* resources.
"""
package = _get_package(package)
_normalize_path(name)
reader = _get_resource_reader(package)
package = _common.get_package(package)
_common.normalize_path(name)
reader = _common.get_resource_reader(package)
if reader is not None:
return reader.is_resource(name)
package_contents = set(contents(package))
@@ -188,16 +140,21 @@ def contents(package: Package) -> Iterable[str]:
not considered resources. Use `is_resource()` on each entry returned here
to check if it is a resource or not.
"""
package = _get_package(package)
reader = _get_resource_reader(package)
package = _common.get_package(package)
reader = _common.get_resource_reader(package)
if reader is not None:
return reader.contents()
# Is the package a namespace package? By definition, namespace packages
# cannot have resources.
namespace = (
package.__spec__.origin is None or
package.__spec__.origin == 'namespace'
)
if namespace or not package.__spec__.has_location:
return ()
return list(item.name for item in _common.from_package(package).iterdir())
return _ensure_sequence(reader.contents())
transversable = _common.from_package(package)
if transversable.is_dir():
return list(item.name for item in transversable.iterdir())
return []
@singledispatch
def _ensure_sequence(iterable):
return list(iterable)
@_ensure_sequence.register(Sequence)
def _(iterable):
return iterable
+1 -1
View File
@@ -136,7 +136,7 @@ class TraversableResources(ResourceReader):
raise FileNotFoundError(resource)
def is_resource(self, path):
return self.files().joinpath(path).isfile()
return self.files().joinpath(path).is_file()
def contents(self):
return (item.name for item in self.files().iterdir())
View File
+123
View File
@@ -0,0 +1,123 @@
import os.path
from collections import OrderedDict
from . import abc
from ._compat import Path, ZipPath
from ._compat import FileNotFoundError, NotADirectoryError
class FileReader(abc.TraversableResources):
def __init__(self, loader):
self.path = Path(loader.path).parent
def resource_path(self, resource):
"""
Return the file system path to prevent
`resources.path()` from creating a temporary
copy.
"""
return str(self.path.joinpath(resource))
def files(self):
return self.path
class ZipReader(abc.TraversableResources):
def __init__(self, loader, module):
_, _, name = module.rpartition('.')
self.prefix = loader.prefix.replace('\\', '/') + name + '/'
self.archive = loader.archive
def open_resource(self, resource):
try:
return super().open_resource(resource)
except KeyError as exc:
raise FileNotFoundError(exc.args[0])
def is_resource(self, path):
# workaround for `zipfile.Path.is_file` returning true
# for non-existent paths.
target = self.files().joinpath(path)
return target.is_file() and target.exists()
def files(self):
return ZipPath(self.archive, self.prefix)
class MultiplexedPath(abc.Traversable):
"""
Given a series of Traversable objects, implement a merged
version of the interface across all objects. Useful for
namespace packages which may be multihomed at a single
name.
"""
def __init__(self, *paths):
paths = list(OrderedDict.fromkeys(paths)) # remove duplicates
self._paths = list(map(Path, paths))
if not self._paths:
message = 'MultiplexedPath must contain at least one path'
raise FileNotFoundError(message)
if any(not path.is_dir() for path in self._paths):
raise NotADirectoryError(
'MultiplexedPath only supports directories')
def iterdir(self):
visited = []
for path in self._paths:
for file in path.iterdir():
if file.name in visited:
continue
visited.append(file.name)
yield file
def read_bytes(self):
raise FileNotFoundError('{} is not a file'.format(self))
def read_text(self, *args, **kwargs):
raise FileNotFoundError('{} is not a file'.format(self))
def is_dir(self):
return True
def is_file(self):
return False
def joinpath(self, child):
# first try to find child in current paths
for file in self.iterdir():
if file.name == child:
return file
# if it does not exist, construct it with the first path
return self._paths[0] / child
__truediv__ = joinpath
def open(self, *args, **kwargs):
raise FileNotFoundError('{} is not a file'.format(self))
def name(self):
return os.path.basename(self._paths[0])
def __repr__(self):
return 'MultiplexedPath({})'.format(
', '.join("'{}'".format(path) for path in self._paths))
class NamespaceReader(abc.TraversableResources):
def __init__(self, namespace_path):
if 'NamespacePath' not in str(namespace_path):
raise ValueError('Invalid path')
self.path = MultiplexedPath(*list(namespace_path))
def resource_path(self, resource):
"""
Return the file system path to prevent
`resources.path()` from creating a temporary
copy.
"""
return str(self.path.joinpath(resource))
def files(self):
return self.path
+2 -2
View File
@@ -18,10 +18,10 @@ __title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
__version__ = "20.3"
__version__ = "20.4"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
__license__ = "BSD-2-Clause or Apache-2.0"
__copyright__ = "Copyright 2014-2019 %s" % __author__
+2 -2
View File
@@ -5,9 +5,9 @@ from __future__ import absolute_import, division, print_function
import sys
from ._typing import MYPY_CHECK_RUNNING
from ._typing import TYPE_CHECKING
if MYPY_CHECK_RUNNING: # pragma: no cover
if TYPE_CHECKING: # pragma: no cover
from typing import Any, Dict, Tuple, Type
+19 -10
View File
@@ -18,22 +18,31 @@ curious maintainer can reach here to read this.
In packaging, all static-typing related imports should be guarded as follows:
from packaging._typing import MYPY_CHECK_RUNNING
from packaging._typing import TYPE_CHECKING
if MYPY_CHECK_RUNNING:
if TYPE_CHECKING:
from typing import ...
Ref: https://github.com/python/mypy/issues/3216
"""
MYPY_CHECK_RUNNING = False
__all__ = ["TYPE_CHECKING", "cast"]
if MYPY_CHECK_RUNNING: # pragma: no cover
import typing
cast = typing.cast
# The TYPE_CHECKING constant defined by the typing module is False at runtime
# but True while type checking.
if False: # pragma: no cover
from typing import TYPE_CHECKING
else:
# typing's cast() is needed at runtime, but we don't want to import typing.
# Thus, we use a dummy no-op version, which we tell mypy to ignore.
def cast(type_, value): # type: ignore
TYPE_CHECKING = False
# typing's cast syntax requires calling typing.cast at runtime, but we don't
# want to import typing at runtime. Here, we inform the type checkers that
# we're importing `typing.cast` as `cast` and re-implement typing.cast's
# runtime behavior in a block that is ignored by type checkers.
if TYPE_CHECKING: # pragma: no cover
# not executed at runtime
from typing import cast
else:
# executed at runtime
def cast(type_, value): # noqa
return value
+2 -2
View File
@@ -13,10 +13,10 @@ from pyparsing import ZeroOrMore, Group, Forward, QuotedString
from pyparsing import Literal as L # noqa
from ._compat import string_types
from ._typing import MYPY_CHECK_RUNNING
from ._typing import TYPE_CHECKING
from .specifiers import Specifier, InvalidSpecifier
if MYPY_CHECK_RUNNING: # pragma: no cover
if TYPE_CHECKING: # pragma: no cover
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
Operator = Callable[[str, str], bool]
+2 -2
View File
@@ -11,11 +11,11 @@ from pyparsing import ZeroOrMore, Word, Optional, Regex, Combine
from pyparsing import Literal as L # noqa
from six.moves.urllib import parse as urlparse
from ._typing import MYPY_CHECK_RUNNING
from ._typing import TYPE_CHECKING
from .markers import MARKER_EXPR, Marker
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
if MYPY_CHECK_RUNNING: # pragma: no cover
if TYPE_CHECKING: # pragma: no cover
from typing import List
+20 -6
View File
@@ -9,10 +9,11 @@ import itertools
import re
from ._compat import string_types, with_metaclass
from ._typing import MYPY_CHECK_RUNNING
from ._typing import TYPE_CHECKING
from .utils import canonicalize_version
from .version import Version, LegacyVersion, parse
if MYPY_CHECK_RUNNING: # pragma: no cover
if TYPE_CHECKING: # pragma: no cover
from typing import (
List,
Dict,
@@ -132,9 +133,14 @@ class _IndividualSpecifier(BaseSpecifier):
# type: () -> str
return "{0}{1}".format(*self._spec)
@property
def _canonical_spec(self):
# type: () -> Tuple[str, Union[Version, str]]
return self._spec[0], canonicalize_version(self._spec[1])
def __hash__(self):
# type: () -> int
return hash(self._spec)
return hash(self._canonical_spec)
def __eq__(self, other):
# type: (object) -> bool
@@ -146,7 +152,7 @@ class _IndividualSpecifier(BaseSpecifier):
elif not isinstance(other, self.__class__):
return NotImplemented
return self._spec == other._spec
return self._canonical_spec == other._canonical_spec
def __ne__(self, other):
# type: (object) -> bool
@@ -510,12 +516,20 @@ class Specifier(_IndividualSpecifier):
@_require_version_compare
def _compare_less_than_equal(self, prospective, spec):
# type: (ParsedVersion, str) -> bool
return prospective <= Version(spec)
# NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from
# the prospective version.
return Version(prospective.public) <= Version(spec)
@_require_version_compare
def _compare_greater_than_equal(self, prospective, spec):
# type: (ParsedVersion, str) -> bool
return prospective >= Version(spec)
# NB: Local version identifiers are NOT permitted in the version
# specifier, so local version labels can be universally removed from
# the prospective version.
return Version(prospective.public) >= Version(spec)
@_require_version_compare
def _compare_less_than(self, prospective, spec_str):
+15 -3
View File
@@ -22,9 +22,9 @@ import sys
import sysconfig
import warnings
from ._typing import MYPY_CHECK_RUNNING, cast
from ._typing import TYPE_CHECKING, cast
if MYPY_CHECK_RUNNING: # pragma: no cover
if TYPE_CHECKING: # pragma: no cover
from typing import (
Dict,
FrozenSet,
@@ -58,6 +58,12 @@ _32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
class Tag(object):
"""
A representation of the tag triple for a wheel.
Instances are considered immutable and thus are hashable. Equality checking
is also supported.
"""
__slots__ = ["_interpreter", "_abi", "_platform"]
@@ -108,6 +114,12 @@ class Tag(object):
def parse_tag(tag):
# type: (str) -> FrozenSet[Tag]
"""
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
Returning a set is required due to the possibility that the tag is a
compressed tag set.
"""
tags = set()
interpreters, abis, platforms = tag.split("-")
for interpreter in interpreters.split("."):
@@ -541,7 +553,7 @@ class _ELFFileHeader(object):
def unpack(fmt):
# type: (str) -> int
try:
result, = struct.unpack(
(result,) = struct.unpack(
fmt, file.read(struct.calcsize(fmt))
) # type: (int, )
except struct.error:
+8 -5
View File
@@ -5,19 +5,22 @@ from __future__ import absolute_import, division, print_function
import re
from ._typing import MYPY_CHECK_RUNNING
from ._typing import TYPE_CHECKING, cast
from .version import InvalidVersion, Version
if MYPY_CHECK_RUNNING: # pragma: no cover
from typing import Union
if TYPE_CHECKING: # pragma: no cover
from typing import NewType, Union
NormalizedName = NewType("NormalizedName", str)
_canonicalize_regex = re.compile(r"[-_.]+")
def canonicalize_name(name):
# type: (str) -> str
# type: (str) -> NormalizedName
# This is taken from PEP 503.
return _canonicalize_regex.sub("-", name).lower()
value = _canonicalize_regex.sub("-", name).lower()
return cast("NormalizedName", value)
def canonicalize_version(_version):
+2 -2
View File
@@ -8,9 +8,9 @@ import itertools
import re
from ._structures import Infinity, NegativeInfinity
from ._typing import MYPY_CHECK_RUNNING
from ._typing import TYPE_CHECKING
if MYPY_CHECK_RUNNING: # pragma: no cover
if TYPE_CHECKING: # pragma: no cover
from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
from ._structures import InfinityType, NegativeInfinityType
+362 -237
View File
@@ -9,30 +9,38 @@ and ``with_pattern()`` when ``import \*`` is used:
From there it's a simple thing to parse a string:
>>> parse("It's {}, I love it!", "It's spam, I love it!")
<Result ('spam',) {}>
>>> _[0]
'spam'
.. code-block:: pycon
>>> parse("It's {}, I love it!", "It's spam, I love it!")
<Result ('spam',) {}>
>>> _[0]
'spam'
Or to search a string for some pattern:
>>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n')
<Result (42,) {}>
.. code-block:: pycon
>>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n')
<Result (42,) {}>
Or find all the occurrences of some pattern in a string:
>>> ''.join(r[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>"))
'the bold text'
.. code-block:: pycon
>>> ''.join(r[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>"))
'the bold text'
If you're going to use the same pattern to match lots of strings you can
compile it once:
>>> from parse import compile
>>> p = compile("It's {}, I love it!")
>>> print(p)
<Parser "It's {}, I love it!">
>>> p.parse("It's spam, I love it!")
<Result ('spam',) {}>
.. code-block:: pycon
>>> from parse import compile
>>> p = compile("It's {}, I love it!")
>>> print(p)
<Parser "It's {}, I love it!">
>>> p.parse("It's spam, I love it!")
<Result ('spam',) {}>
("compile" is not exported for ``import *`` usage as it would override the
built-in ``compile()`` function)
@@ -40,8 +48,10 @@ built-in ``compile()`` function)
The default behaviour is to match strings case insensitively. You may match with
case by specifying `case_sensitive=True`:
>>> parse('SPAM', 'spam', case_sensitive=True) is None
True
.. code-block:: pycon
>>> parse('SPAM', 'spam', case_sensitive=True) is None
True
Format Syntax
@@ -64,40 +74,44 @@ There are no "!" field conversions like ``format()`` has.
Some simple parse() format string examples:
>>> parse("Bring me a {}", "Bring me a shrubbery")
<Result ('shrubbery',) {}>
>>> r = parse("The {} who say {}", "The knights who say Ni!")
>>> print(r)
<Result ('knights', 'Ni!') {}>
>>> print(r.fixed)
('knights', 'Ni!')
>>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade")
>>> print(r)
<Result () {'item': 'hand grenade'}>
>>> print(r.named)
{'item': 'hand grenade'}
>>> print(r['item'])
hand grenade
>>> 'item' in r
True
.. code-block:: pycon
>>> parse("Bring me a {}", "Bring me a shrubbery")
<Result ('shrubbery',) {}>
>>> r = parse("The {} who say {}", "The knights who say Ni!")
>>> print(r)
<Result ('knights', 'Ni!') {}>
>>> print(r.fixed)
('knights', 'Ni!')
>>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade")
>>> print(r)
<Result () {'item': 'hand grenade'}>
>>> print(r.named)
{'item': 'hand grenade'}
>>> print(r['item'])
hand grenade
>>> 'item' in r
True
Note that `in` only works if you have named fields. Dotted names and indexes
are possible though the application must make additional sense of the result:
>>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
>>> print(r)
<Result () {'food.type': 'spam'}>
>>> print(r.named)
{'food.type': 'spam'}
>>> print(r['food.type'])
spam
>>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!")
>>> print(r)
<Result () {'quest': {'name': 'to seek the holy grail!'}}>
>>> print(r['quest'])
{'name': 'to seek the holy grail!'}
>>> print(r['quest']['name'])
to seek the holy grail!
.. code-block:: pycon
>>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
>>> print(r)
<Result () {'food.type': 'spam'}>
>>> print(r.named)
{'food.type': 'spam'}
>>> print(r['food.type'])
spam
>>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!")
>>> print(r)
<Result () {'quest': {'name': 'to seek the holy grail!'}}>
>>> print(r['quest'])
{'name': 'to seek the holy grail!'}
>>> print(r['quest']['name'])
to seek the holy grail!
If the text you're matching has braces in it you can match those by including
a double-brace ``{{`` or ``}}`` in your format string, just like format() does.
@@ -174,18 +188,22 @@ tt Time time
Some examples of typed parsing with ``None`` returned if the typing
does not match:
>>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...')
<Result (3, 'weapons') {}>
>>> parse('Our {:d} {:w} are...', 'Our three weapons are...')
>>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM')
<Result (datetime.datetime(2011, 2, 1, 23, 0),) {}>
.. code-block:: pycon
>>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...')
<Result (3, 'weapons') {}>
>>> parse('Our {:d} {:w} are...', 'Our three weapons are...')
>>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM')
<Result (datetime.datetime(2011, 2, 1, 23, 0),) {}>
And messing about with alignment:
>>> parse('with {:>} herring', 'with a herring')
<Result ('a',) {}>
>>> parse('spam {:^} spam', 'spam lovely spam')
<Result ('lovely',) {}>
.. code-block:: pycon
>>> parse('with {:>} herring', 'with a herring')
<Result ('a',) {}>
>>> parse('spam {:^} spam', 'spam lovely spam')
<Result ('lovely',) {}>
Note that the "center" alignment does not test to make sure the value is
centered - it just strips leading and trailing whitespace.
@@ -194,14 +212,16 @@ Width and precision may be used to restrict the size of matched text
from the input. Width specifies a minimum size and precision specifies
a maximum. For example:
>>> parse('{:.2}{:.2}', 'look') # specifying precision
<Result ('lo', 'ok') {}>
>>> parse('{:4}{:4}', 'look at that') # specifying width
<Result ('look', 'at that') {}>
>>> parse('{:4}{:.4}', 'look at that') # specifying both
<Result ('look at ', 'that') {}>
>>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers
<Result (4, 40) {}>
.. code-block:: pycon
>>> parse('{:.2}{:.2}', 'look') # specifying precision
<Result ('lo', 'ok') {}>
>>> parse('{:4}{:4}', 'look at that') # specifying width
<Result ('look', 'at that') {}>
>>> parse('{:4}{:.4}', 'look at that') # specifying both
<Result ('look at ', 'that') {}>
>>> parse('{:2d}{:2d}', '0440') # parsing two contiguous numbers
<Result (4, 40) {}>
Some notes for the date and time types:
@@ -246,18 +266,18 @@ The result of a ``parse()`` and ``search()`` operation is either ``None`` (no ma
The ``Result`` instance has three attributes:
fixed
``fixed``
A tuple of the fixed-position, anonymous fields extracted from the input.
named
``named``
A dictionary of the named fields extracted from the input.
spans
``spans``
A dictionary mapping the names and fixed position indices matched to a
2-tuple slice range of where the match occurred in the input.
The span does not include any stripped padding (alignment or width).
The ``Match`` instance has one method:
evaluate_result()
``evaluate_result()``
Generates and returns a ``Result`` instance for this ``Match`` object.
@@ -273,56 +293,66 @@ The converter will be passed the field string matched. Whatever it returns
will be substituted in the ``Result`` instance for that field.
Your custom type conversions may override the builtin types if you supply one
with the same identifier.
with the same identifier:
>>> def shouty(string):
... return string.upper()
...
>>> parse('{:shouty} world', 'hello world', dict(shouty=shouty))
<Result ('HELLO',) {}>
.. code-block:: pycon
>>> def shouty(string):
... return string.upper()
...
>>> parse('{:shouty} world', 'hello world', dict(shouty=shouty))
<Result ('HELLO',) {}>
If the type converter has the optional ``pattern`` attribute, it is used as
regular expression for better pattern matching (instead of the default one).
regular expression for better pattern matching (instead of the default one):
>>> def parse_number(text):
... return int(text)
>>> parse_number.pattern = r'\d+'
>>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
<Result () {'number': 42}>
>>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number))
>>> assert _ is None, "MISMATCH"
.. code-block:: pycon
>>> def parse_number(text):
... return int(text)
>>> parse_number.pattern = r'\d+'
>>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
<Result () {'number': 42}>
>>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number))
>>> assert _ is None, "MISMATCH"
You can also use the ``with_pattern(pattern)`` decorator to add this
information to a type converter function:
>>> from parse import with_pattern
>>> @with_pattern(r'\d+')
... def parse_number(text):
... return int(text)
>>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
<Result () {'number': 42}>
.. code-block:: pycon
>>> from parse import with_pattern
>>> @with_pattern(r'\d+')
... def parse_number(text):
... return int(text)
>>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
<Result () {'number': 42}>
A more complete example of a custom type might be:
>>> yesno_mapping = {
... "yes": True, "no": False,
... "on": True, "off": False,
... "true": True, "false": False,
... }
>>> @with_pattern(r"|".join(yesno_mapping))
... def parse_yesno(text):
... return yesno_mapping[text.lower()]
.. code-block:: pycon
>>> yesno_mapping = {
... "yes": True, "no": False,
... "on": True, "off": False,
... "true": True, "false": False,
... }
>>> @with_pattern(r"|".join(yesno_mapping))
... def parse_yesno(text):
... return yesno_mapping[text.lower()]
If the type converter ``pattern`` uses regex-grouping (with parenthesis),
you should indicate this by using the optional ``regex_group_count`` parameter
in the ``with_pattern()`` decorator:
>>> @with_pattern(r'((\d+))', regex_group_count=2)
... def parse_number2(text):
... return int(text)
>>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
<Result (42, 43) {}>
.. code-block:: pycon
>>> @with_pattern(r'((\d+))', regex_group_count=2)
... def parse_number2(text):
... return int(text)
>>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
<Result (42, 43) {}>
Otherwise, this may cause parsing problems with unnamed/fixed parameters.
@@ -330,22 +360,26 @@ Otherwise, this may cause parsing problems with unnamed/fixed parameters.
Potential Gotchas
-----------------
`parse()` will always match the shortest text necessary (from left to right)
``parse()`` will always match the shortest text necessary (from left to right)
to fulfil the parse pattern, so for example:
>>> pattern = '{dir1}/{dir2}'
>>> data = 'root/parent/subdir'
>>> sorted(parse(pattern, data).named.items())
[('dir1', 'root'), ('dir2', 'parent/subdir')]
.. code-block:: pycon
>>> pattern = '{dir1}/{dir2}'
>>> data = 'root/parent/subdir'
>>> sorted(parse(pattern, data).named.items())
[('dir1', 'root'), ('dir2', 'parent/subdir')]
So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit
the pattern, the actual match represents the shortest successful match for
`dir1`.
``dir1``.
----
**Version history (in brief)**:
- 1.18.0 Correct bug in int parsing introduced in 1.16.0 (thanks @maxxk)
- 1.17.0 Make left- and center-aligned search consume up to next space
- 1.16.0 Make compiled parse objects pickleable (thanks @martinResearch)
- 1.15.0 Several fixes for parsing non-base 10 numbers (thanks @vladikcomper)
- 1.14.0 More broad acceptance of Fortran number format (thanks @purpleskyfall)
- 1.13.1 Project metadata correction.
@@ -419,12 +453,13 @@ the pattern, the actual match represents the shortest successful match for
and removed the restriction on mixing fixed-position and named fields
- 1.0.0 initial release
This code is copyright 2012-2019 Richard Jones <richard@python.org>
This code is copyright 2012-2020 Richard Jones <richard@python.org>
See the end of the source file for the license of use.
'''
from __future__ import absolute_import
__version__ = '1.15.0'
__version__ = '1.18.0'
# yes, I now have two problems
import re
@@ -461,29 +496,35 @@ def with_pattern(pattern, regex_group_count=None):
:param regex_group_count: Indicates how many regex-groups are in pattern.
:return: wrapped function
"""
def decorator(func):
func.pattern = pattern
func.regex_group_count = regex_group_count
return func
return decorator
def int_convert(base=None):
'''Convert a string to an integer.
class int_convert:
"""Convert a string to an integer.
The string may start with a sign.
It may be of a base other than 2, 8, 10 or 16.
If base isn't specified, it will be detected automatically based
on a string format. When string starts with a base indicator, 0#nnnn,
on a string format. When string starts with a base indicator, 0#nnnn,
it overrides the default base of 10.
It may also have other non-numeric characters that we can ignore.
'''
"""
CHARS = '0123456789abcdefghijklmnopqrstuvwxyz'
def f(string, match, base=base):
def __init__(self, base=None):
self.base = base
def __call__(self, string, match):
if string[0] == '-':
sign = -1
number_start = 1
@@ -494,34 +535,46 @@ def int_convert(base=None):
sign = 1
number_start = 0
base = self.base
# If base wasn't specified, detect it automatically
if base is None:
# Assume decimal number, unless different base is detected
base = 10
# Assume decimal number, unless different base is detected
base = 10
# For number formats starting with 0b, 0o, 0x, use corresponding base ...
if string[number_start] == '0' and len(string) - number_start > 2:
if string[number_start+1] in 'bB':
base = 2
elif string[number_start+1] in 'oO':
base = 8
elif string[number_start+1] in 'xX':
base = 16
# For number formats starting with 0b, 0o, 0x, use corresponding base ...
if string[number_start] == '0' and len(string) - number_start > 2:
if string[number_start + 1] in 'bB':
base = 2
elif string[number_start + 1] in 'oO':
base = 8
elif string[number_start + 1] in 'xX':
base = 16
chars = CHARS[:base]
chars = int_convert.CHARS[: base]
string = re.sub('[^%s]' % chars, '', string.lower())
return sign * int(string, base)
return f
class convert_first:
"""Convert the first element of a pair.
This equivalent to lambda s,m: converter(s). But unlike a lambda function, it can be pickled
"""
def __init__(self, converter):
self.converter = converter
def __call__(self, string, match):
return self.converter(string)
def percentage(string, match):
return float(string[:-1]) / 100.
return float(string[:-1]) / 100.0
class FixedTzOffset(tzinfo):
"""Fixed offset in minutes east from UTC.
"""
"""Fixed offset in minutes east from UTC."""
ZERO = timedelta(0)
def __init__(self, offset, name):
@@ -529,8 +582,7 @@ class FixedTzOffset(tzinfo):
self._name = name
def __repr__(self):
return '<%s %s %s>' % (self.__class__.__name__, self._name,
self._offset)
return '<%s %s %s>' % (self.__class__.__name__, self._name, self._offset)
def utcoffset(self, dt):
return self._offset
@@ -548,18 +600,29 @@ class FixedTzOffset(tzinfo):
MONTHS_MAP = dict(
Jan=1, January=1,
Feb=2, February=2,
Mar=3, March=3,
Apr=4, April=4,
Jan=1,
January=1,
Feb=2,
February=2,
Mar=3,
March=3,
Apr=4,
April=4,
May=5,
Jun=6, June=6,
Jul=7, July=7,
Aug=8, August=8,
Sep=9, September=9,
Oct=10, October=10,
Nov=11, November=11,
Dec=12, December=12
Jun=6,
June=6,
Jul=7,
July=7,
Aug=8,
August=8,
Sep=9,
September=9,
Oct=10,
October=10,
Nov=11,
November=11,
Dec=12,
December=12,
)
DAYS_PAT = r'(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
MONTHS_PAT = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
@@ -569,17 +632,28 @@ AM_PAT = r'(\s+[AP]M)'
TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)'
def date_convert(string, match, ymd=None, mdy=None, dmy=None,
d_m_y=None, hms=None, am=None, tz=None, mm=None, dd=None):
'''Convert the incoming string containing some date / time info into a
def date_convert(
string,
match,
ymd=None,
mdy=None,
dmy=None,
d_m_y=None,
hms=None,
am=None,
tz=None,
mm=None,
dd=None,
):
"""Convert the incoming string containing some date / time info into a
datetime instance.
'''
"""
groups = match.groups()
time_only = False
if mm and dd:
y=datetime.today().year
m=groups[mm]
d=groups[dd]
y = datetime.today().year
m = groups[mm]
d = groups[dd]
elif ymd is not None:
y, m, d = re.split(r'[-/\s]', groups[ymd])
elif mdy is not None:
@@ -670,13 +744,11 @@ class RepeatedNameError(ValueError):
REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])')
# allowed field types
ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') +
['t' + c for c in 'ieahgcts'])
ALLOWED_TYPES = set(list('nbox%fFegwWdDsSl') + ['t' + c for c in 'ieahgcts'])
def extract_format(format, extra_types):
'''Pull apart the format [[fill]align][0][width][.precision][type]
'''
"""Pull apart the format [[fill]align][0][width][.precision][type]"""
fill = align = None
if format[0] in '<>=^':
align = format[0]
@@ -721,8 +793,8 @@ PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})"
class Parser(object):
'''Encapsulate a format string that may be used to parse other strings.
'''
"""Encapsulate a format string that may be used to parse other strings."""
def __init__(self, format, extra_types=None, case_sensitive=False):
# a mapping of a name as in {hello.world} to a regex-group compatible
# name, like hello__world Its used to prevent the transformation of
@@ -756,8 +828,7 @@ class Parser(object):
def __repr__(self):
if len(self._format) > 20:
return '<%s %r>' % (self.__class__.__name__,
self._format[:17] + '...')
return '<%s %r>' % (self.__class__.__name__, self._format[:17] + '...')
return '<%s %r>' % (self.__class__.__name__, self._format)
@property
@@ -769,8 +840,9 @@ class Parser(object):
# access error through sys to keep py3k and backward compat
e = str(sys.exc_info()[1])
if e.endswith('this version only supports 100 named groups'):
raise TooManyFields('sorry, you are attempting to parse '
'too many complex fields')
raise TooManyFields(
'sorry, you are attempting to parse ' 'too many complex fields'
)
return self.__search_re
@property
@@ -783,19 +855,29 @@ class Parser(object):
# access error through sys to keep py3k and backward compat
e = str(sys.exc_info()[1])
if e.endswith('this version only supports 100 named groups'):
raise TooManyFields('sorry, you are attempting to parse '
'too many complex fields')
raise TooManyFields(
'sorry, you are attempting to parse ' 'too many complex fields'
)
except re.error:
raise NotImplementedError("Group names (e.g. (?P<name>) can "
"cause failure, as they are not escaped properly: '%s'" %
expression)
raise NotImplementedError(
"Group names (e.g. (?P<name>) can "
"cause failure, as they are not escaped properly: '%s'" % expression
)
return self.__match_re
@property
def named_fields(self):
return self._named_fields.copy()
@property
def fixed_fields(self):
return self._fixed_fields.copy()
def parse(self, string, evaluate_result=True):
'''Match my format to the string exactly.
"""Match my format to the string exactly.
Return a Result or Match instance or None if there's no match.
'''
"""
m = self._match_re.match(string)
if m is None:
return None
@@ -806,7 +888,7 @@ class Parser(object):
return Match(self, m)
def search(self, string, pos=0, endpos=None, evaluate_result=True):
'''Search the string for my format.
"""Search the string for my format.
Optionally start the search at "pos" character index and limit the
search to a maximum index of endpos - equivalent to
@@ -816,7 +898,7 @@ class Parser(object):
Match instance is returned instead of the actual Result instance.
Return either a Result instance or None if there's no match.
'''
"""
if endpos is None:
endpos = len(string)
m = self._search_re.search(string, pos, endpos)
@@ -828,8 +910,10 @@ class Parser(object):
else:
return Match(self, m)
def findall(self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True):
'''Search "string" for all occurrences of "format".
def findall(
self, string, pos=0, endpos=None, extra_types=None, evaluate_result=True
):
"""Search "string" for all occurrences of "format".
Optionally start the search at "pos" character index and limit the
search to a maximum index of endpos - equivalent to
@@ -837,10 +921,12 @@ class Parser(object):
Returns an iterator that holds Result or Match instances for each format match
found.
'''
"""
if endpos is None:
endpos = len(string)
return ResultIterator(self, string, pos, endpos, evaluate_result=evaluate_result)
return ResultIterator(
self, string, pos, endpos, evaluate_result=evaluate_result
)
def _expand_named_fields(self, named_fields):
result = {}
@@ -854,7 +940,7 @@ class Parser(object):
if subkeys:
for subkey in re.findall(r'\[[^\]]+\]', subkeys):
d = d.setdefault(k,{})
d = d.setdefault(k, {})
k = subkey[1:-1]
# assign the value to the last key
@@ -887,8 +973,7 @@ class Parser(object):
# now figure the match spans
spans = dict((n, m.span(name_map[n])) for n in named_fields)
spans.update((i, m.span(n + 1))
for i, n in enumerate(self._fixed_fields))
spans.update((i, m.span(n + 1)) for i, n in enumerate(self._fixed_fields))
# and that's our result
return Result(fixed_fields, self._expand_named_fields(named_fields), spans)
@@ -949,9 +1034,11 @@ class Parser(object):
name = field
if name in self._name_to_group_map:
if self._name_types[name] != format:
raise RepeatedNameError('field type %r for field "%s" '
'does not match previous seen type %r' % (format,
name, self._name_types[name]))
raise RepeatedNameError(
'field type %r for field "%s" '
'does not match previous seen type %r'
% (format, name, self._name_types[name])
)
group = self._name_to_group_map[name]
# match previously-seen value
return r'(?P=%s)' % group
@@ -986,10 +1073,7 @@ class Parser(object):
if regex_group_count is None:
regex_group_count = 0
self._group_index += regex_group_count
def f(string, m):
return type_converter(string)
self._type_conversions[group] = f
self._type_conversions[group] = convert_first(type_converter)
elif type == 'n':
s = r'\d{1,3}([,.]\d{3})*'
self._group_index += 1
@@ -1012,79 +1096,104 @@ class Parser(object):
self._type_conversions[group] = percentage
elif type == 'f':
s = r'\d*\.\d+'
self._type_conversions[group] = lambda s, m: float(s)
self._type_conversions[group] = convert_first(float)
elif type == 'F':
s = r'\d*\.\d+'
self._type_conversions[group] = lambda s, m: Decimal(s)
self._type_conversions[group] = convert_first(Decimal)
elif type == 'e':
s = r'\d*\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF'
self._type_conversions[group] = lambda s, m: float(s)
self._type_conversions[group] = convert_first(float)
elif type == 'g':
s = r'\d+(\.\d+)?([eE][-+]?\d+)?|nan|NAN|[-+]?inf|[-+]?INF'
self._group_index += 2
self._type_conversions[group] = lambda s, m: float(s)
self._type_conversions[group] = convert_first(float)
elif type == 'd':
if format.get('width'):
width = r'{1,%s}' % int(format['width'])
else:
width = '+'
s = r'\d{w}|[-+ ]?0[xX][0-9a-fA-F]{w}|[-+ ]?0[bB][01]{w}|[-+ ]?0[oO][0-7]{w}'.format(w=width)
self._type_conversions[group] = int_convert() # do not specify numeber base, determine it automatically
s = r'\d{w}|[-+ ]?0[xX][0-9a-fA-F]{w}|[-+ ]?0[bB][01]{w}|[-+ ]?0[oO][0-7]{w}'.format(
w=width
)
self._type_conversions[
group
] = int_convert() # do not specify number base, determine it automatically
elif type == 'ti':
s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % \
TIME_PAT
s = r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' % TIME_PAT
n = self._group_index
self._type_conversions[group] = partial(date_convert, ymd=n + 1,
hms=n + 4, tz=n + 7)
self._type_conversions[group] = partial(
date_convert, ymd=n + 1, hms=n + 4, tz=n + 7
)
self._group_index += 7
elif type == 'tg':
s = r'(\d{1,2}[-/](\d{1,2}|%s)[-/]\d{4})(\s+%s)?%s?%s?' % (
ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT)
ALL_MONTHS_PAT,
TIME_PAT,
AM_PAT,
TZ_PAT,
)
n = self._group_index
self._type_conversions[group] = partial(date_convert, dmy=n + 1,
hms=n + 5, am=n + 8, tz=n + 9)
self._type_conversions[group] = partial(
date_convert, dmy=n + 1, hms=n + 5, am=n + 8, tz=n + 9
)
self._group_index += 9
elif type == 'ta':
s = r'((\d{1,2}|%s)[-/]\d{1,2}[-/]\d{4})(\s+%s)?%s?%s?' % (
ALL_MONTHS_PAT, TIME_PAT, AM_PAT, TZ_PAT)
ALL_MONTHS_PAT,
TIME_PAT,
AM_PAT,
TZ_PAT,
)
n = self._group_index
self._type_conversions[group] = partial(date_convert, mdy=n + 1,
hms=n + 5, am=n + 8, tz=n + 9)
self._type_conversions[group] = partial(
date_convert, mdy=n + 1, hms=n + 5, am=n + 8, tz=n + 9
)
self._group_index += 9
elif type == 'te':
# this will allow microseconds through if they're present, but meh
s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % (DAYS_PAT,
MONTHS_PAT, TIME_PAT, TZ_PAT)
s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % (
DAYS_PAT,
MONTHS_PAT,
TIME_PAT,
TZ_PAT,
)
n = self._group_index
self._type_conversions[group] = partial(date_convert, dmy=n + 3,
hms=n + 5, tz=n + 8)
self._type_conversions[group] = partial(
date_convert, dmy=n + 3, hms=n + 5, tz=n + 8
)
self._group_index += 8
elif type == 'th':
# slight flexibility here from the stock Apache format
s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT,
TZ_PAT)
s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % (MONTHS_PAT, TIME_PAT, TZ_PAT)
n = self._group_index
self._type_conversions[group] = partial(date_convert, dmy=n + 1,
hms=n + 3, tz=n + 6)
self._type_conversions[group] = partial(
date_convert, dmy=n + 1, hms=n + 3, tz=n + 6
)
self._group_index += 6
elif type == 'tc':
s = r'(%s)\s+%s\s+(\d{1,2})\s+%s\s+(\d{4})' % (
DAYS_PAT, MONTHS_PAT, TIME_PAT)
DAYS_PAT,
MONTHS_PAT,
TIME_PAT,
)
n = self._group_index
self._type_conversions[group] = partial(date_convert,
d_m_y=(n + 4, n + 3, n + 8), hms=n + 5)
self._type_conversions[group] = partial(
date_convert, d_m_y=(n + 4, n + 3, n + 8), hms=n + 5
)
self._group_index += 8
elif type == 'tt':
s = r'%s?%s?%s?' % (TIME_PAT, AM_PAT, TZ_PAT)
n = self._group_index
self._type_conversions[group] = partial(date_convert, hms=n + 1,
am=n + 4, tz=n + 5)
self._type_conversions[group] = partial(
date_convert, hms=n + 1, am=n + 4, tz=n + 5
)
self._group_index += 5
elif type == 'ts':
s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT
n = self._group_index
self._type_conversions[group] = partial(date_convert, mm=n+1, dd=n+3,
hms=n + 5)
self._type_conversions[group] = partial(
date_convert, mm=n + 1, dd=n + 3, hms=n + 5
)
self._group_index += 5
elif type == 'l':
s = r'[A-Za-z]+'
@@ -1138,24 +1247,25 @@ class Parser(object):
# align "=" has been handled
if align == '<':
s = '%s%s*' % (s, fill)
s = '%s%s+' % (s, fill)
elif align == '>':
s = '%s*%s' % (fill, s)
elif align == '^':
s = '%s*%s%s*' % (fill, s, fill)
s = '%s*%s%s+' % (fill, s, fill)
return s
class Result(object):
'''The result of a parse() or search().
"""The result of a parse() or search().
Fixed results may be looked up using `result[index]`.
Named results may be looked up using `result['name']`.
Named results may be tested for existence using `'name' in result`.
'''
"""
def __init__(self, fixed, named, spans):
self.fixed = fixed
self.named = named
@@ -1167,19 +1277,19 @@ class Result(object):
return self.named[item]
def __repr__(self):
return '<%s %r %r>' % (self.__class__.__name__, self.fixed,
self.named)
return '<%s %r %r>' % (self.__class__.__name__, self.fixed, self.named)
def __contains__(self, name):
return name in self.named
class Match(object):
'''The result of a parse() or search() if no results are generated.
"""The result of a parse() or search() if no results are generated.
This class is only used to expose internal used regex match objects
to the user and use them for external Parser.evaluate_result calls.
'''
"""
def __init__(self, parser, match):
self.parser = parser
self.match = match
@@ -1190,10 +1300,11 @@ class Match(object):
class ResultIterator(object):
'''The result of a findall() operation.
"""The result of a findall() operation.
Each element is a Result instance.
'''
"""
def __init__(self, parser, string, pos, endpos, evaluate_result=True):
self.parser = parser
self.string = string
@@ -1220,7 +1331,7 @@ class ResultIterator(object):
def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False):
'''Using "format" attempt to pull values from "string".
"""Using "format" attempt to pull values from "string".
The format must match the string contents exactly. If the value
you're looking for is instead just a part of the string use
@@ -1244,14 +1355,21 @@ def parse(format, string, extra_types=None, evaluate_result=True, case_sensitive
See the module documentation for the use of "extra_types".
In the case there is no match parse() will return None.
'''
"""
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
return p.parse(string, evaluate_result=evaluate_result)
def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True,
case_sensitive=False):
'''Search "string" for the first occurrence of "format".
def search(
format,
string,
pos=0,
endpos=None,
extra_types=None,
evaluate_result=True,
case_sensitive=False,
):
"""Search "string" for the first occurrence of "format".
The format may occur anywhere within the string. If
instead you wish for the format to exactly match the string
@@ -1278,14 +1396,21 @@ def search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result
See the module documentation for the use of "extra_types".
In the case there is no match parse() will return None.
'''
"""
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
return p.search(string, pos, endpos, evaluate_result=evaluate_result)
def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True,
case_sensitive=False):
'''Search "string" for all occurrences of "format".
def findall(
format,
string,
pos=0,
endpos=None,
extra_types=None,
evaluate_result=True,
case_sensitive=False,
):
"""Search "string" for all occurrences of "format".
You will be returned an iterator that holds Result instances
for each format match found.
@@ -1309,13 +1434,13 @@ def findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_resul
If the format is invalid a ValueError will be raised.
See the module documentation for the use of "extra_types".
'''
"""
p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
return Parser(format, extra_types=extra_types).findall(string, pos, endpos, evaluate_result=evaluate_result)
return p.findall(string, pos, endpos, evaluate_result=evaluate_result)
def compile(format, extra_types=None, case_sensitive=False):
'''Create a Parser instance to parse "format".
"""Create a Parser instance to parse "format".
The resultant Parser has a method .parse(string) which
behaves in the same manner as parse(format, string).
@@ -1329,7 +1454,7 @@ def compile(format, extra_types=None, case_sensitive=False):
See the module documentation for the use of "extra_types".
Returns a Parser instance.
'''
"""
return Parser(format, extra_types=extra_types, case_sensitive=case_sensitive)
+3 -1
View File
@@ -1,4 +1,6 @@
"""Wrappers to build Python packages using PEP 517 hooks
"""
__version__ = '0.8.2'
__version__ = '0.9.1'
from .wrappers import * # noqa: F401, F403
+33 -14
View File
@@ -9,6 +9,15 @@ from tempfile import mkdtemp
from . import compat
__all__ = [
'BackendUnavailable',
'BackendInvalid',
'HookMissing',
'UnsupportedOperation',
'default_subprocess_runner',
'quiet_subprocess_runner',
'Pep517HookCaller',
]
try:
import importlib.resources as resources
@@ -102,19 +111,22 @@ def norm_and_check(source_tree, requested):
class Pep517HookCaller(object):
"""A wrapper around a source directory to be built with a PEP 517 backend.
source_dir : The path to the source directory, containing pyproject.toml.
build_backend : The build backend spec, as per PEP 517, from
:param source_dir: The path to the source directory, containing
pyproject.toml.
backend_path : The backend path, as per PEP 517, from pyproject.toml.
runner : A callable that invokes the wrapper subprocess.
:param build_backend: The build backend spec, as per PEP 517, from
pyproject.toml.
:param backend_path: The backend path, as per PEP 517, from pyproject.toml.
:param runner: A callable that invokes the wrapper subprocess.
:param python_executable: The Python executable used to invoke the backend
The 'runner', if provided, must expect the following:
cmd : a list of strings representing the command and arguments to
execute, as would be passed to e.g. 'subprocess.check_call'.
cwd : a string representing the working directory that must be
used for the subprocess. Corresponds to the provided source_dir.
extra_environ : a dict mapping environment variable names to values
which must be set for the subprocess execution.
- cmd: a list of strings representing the command and arguments to
execute, as would be passed to e.g. 'subprocess.check_call'.
- cwd: a string representing the working directory that must be
used for the subprocess. Corresponds to the provided source_dir.
- extra_environ: a dict mapping environment variable names to values
which must be set for the subprocess execution.
"""
def __init__(
self,
@@ -122,6 +134,7 @@ class Pep517HookCaller(object):
build_backend,
backend_path=None,
runner=None,
python_executable=None,
):
if runner is None:
runner = default_subprocess_runner
@@ -134,6 +147,9 @@ class Pep517HookCaller(object):
]
self.backend_path = backend_path
self._subprocess_runner = runner
if not python_executable:
python_executable = sys.executable
self.python_executable = python_executable
@contextmanager
def subprocess_runner(self, runner):
@@ -150,7 +166,8 @@ class Pep517HookCaller(object):
def get_requires_for_build_wheel(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
Returns a list of dependency specifications, e.g.::
["wheel >= 0.25", "setuptools"]
This does not include requirements specified in pyproject.toml.
@@ -164,7 +181,7 @@ class Pep517HookCaller(object):
def prepare_metadata_for_build_wheel(
self, metadata_directory, config_settings=None,
_allow_fallback=True):
"""Prepare a *.dist-info folder with metadata for this project.
"""Prepare a ``*.dist-info`` folder with metadata for this project.
Returns the name of the newly created folder.
@@ -202,7 +219,8 @@ class Pep517HookCaller(object):
def get_requires_for_build_sdist(self, config_settings=None):
"""Identify packages required for building a wheel
Returns a list of dependency specifications, e.g.:
Returns a list of dependency specifications, e.g.::
["setuptools >= 26"]
This does not include requirements specified in pyproject.toml.
@@ -252,8 +270,9 @@ class Pep517HookCaller(object):
# Run the hook in a subprocess
with _in_proc_script_path() as script:
python = self.python_executable
self._subprocess_runner(
[sys.executable, str(script), hook_name, td],
[python, abspath(str(script)), hook_name, td],
cwd=self.source_dir,
extra_environ=extra_environ
)
+1 -1
View File
@@ -22,7 +22,7 @@ import pkg_resources
# from graphviz import backend, Digraph
__version__ = '0.13.2'
__version__ = '1.0.0'
flatten = chain.from_iterable
+172 -10
View File
@@ -1,13 +1,175 @@
Copyright 2019 Kenneth Reitz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
https://www.apache.org/licenses/LICENSE-2.0
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
+16 -8
View File
@@ -57,10 +57,10 @@ def check_compatibility(urllib3_version, chardet_version):
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1, <= 1.25
# urllib3 >= 1.21.1, <= 1.26
assert major == 1
assert minor >= 21
assert minor <= 25
assert minor <= 26
# Check chardet for compatibility.
major, minor, patch = chardet_version.split('.')[:3]
@@ -90,14 +90,22 @@ except (AssertionError, ValueError):
"version!".format(urllib3.__version__, chardet.__version__),
RequestsDependencyWarning)
# Attempt to enable urllib3's SNI support, if possible
# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
try:
import ssl
except ImportError:
ssl = None
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
if not getattr(ssl, "HAS_SNI", False):
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
+2 -2
View File
@@ -5,8 +5,8 @@
__title__ = 'requests'
__description__ = 'Python HTTP for Humans.'
__url__ = 'https://requests.readthedocs.io'
__version__ = '2.23.0'
__build__ = 0x022300
__version__ = '2.25.0'
__build__ = 0x022500
__author__ = 'Kenneth Reitz'
__author_email__ = 'me@kennethreitz.org'
__license__ = 'Apache 2.0'
+3 -6
View File
@@ -94,11 +94,11 @@ class ChunkedEncodingError(RequestException):
class ContentDecodingError(RequestException, BaseHTTPError):
"""Failed to decode response content"""
"""Failed to decode response content."""
class StreamConsumedError(RequestException, TypeError):
"""The content for this response was already consumed"""
"""The content for this response was already consumed."""
class RetryError(RequestException):
@@ -106,21 +106,18 @@ class RetryError(RequestException):
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body"""
"""Requests encountered an error when trying to rewind a body."""
# Warnings
class RequestsWarning(Warning):
"""Base warning for Requests."""
pass
class FileModeWarning(RequestsWarning, DeprecationWarning):
"""A file was opened in text mode, but Requests determined its binary length."""
pass
class RequestsDependencyWarning(RequestsWarning):
"""An imported dependency doesn't match the expected version range."""
pass
+9 -7
View File
@@ -273,7 +273,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
"""The fully mutable :class:`PreparedRequest <PreparedRequest>` object,
containing the exact bytes that will be sent to the server.
Generated from either a :class:`Request <Request>` object or manually.
Instances are generated from a :class:`Request <Request>` object, and
should not be instantiated manually; doing so may produce undesirable
effects.
Usage::
@@ -473,12 +475,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
not isinstance(data, (basestring, list, tuple, Mapping))
])
try:
length = super_len(data)
except (TypeError, AttributeError, UnsupportedOperation):
length = None
if is_stream:
try:
length = super_len(data)
except (TypeError, AttributeError, UnsupportedOperation):
length = None
body = data
if getattr(body, 'tell', None) is not None:
@@ -916,7 +918,7 @@ class Response(object):
return l
def raise_for_status(self):
"""Raises stored :class:`HTTPError`, if one occurred."""
"""Raises :class:`HTTPError`, if one occurred."""
http_error_msg = ''
if isinstance(self.reason, bytes):
+19 -5
View File
@@ -387,6 +387,13 @@ class Session(SessionRedirectMixin):
self.stream = False
#: SSL Verification default.
#: Defaults to `True`, requiring requests to verify the TLS certificate at the
#: remote end.
#: If verify is set to `False`, requests will accept any TLS certificate
#: presented by the server, and will ignore hostname mismatches and/or
#: expired certificates, which will make your application vulnerable to
#: man-in-the-middle (MitM) attacks.
#: Only set this to `False` for testing.
self.verify = True
#: SSL client certificate default, if String, path to ssl client
@@ -495,7 +502,12 @@ class Session(SessionRedirectMixin):
content. Defaults to ``False``.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
to a CA bundle to use. Defaults to ``True``. When set to
``False``, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to
man-in-the-middle (MitM) attacks. Setting verify to ``False``
may be useful during local development or testing.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
:rtype: requests.Response
@@ -658,11 +670,13 @@ class Session(SessionRedirectMixin):
extract_cookies_to_jar(self.cookies, request, r.raw)
# Redirect resolving generator.
gen = self.resolve_redirects(r, request, **kwargs)
# Resolve redirects if allowed.
history = [resp for resp in gen] if allow_redirects else []
if allow_redirects:
# Redirect resolving generator.
gen = self.resolve_redirects(r, request, **kwargs)
history = [resp for resp in gen]
else:
history = []
# Shuffle things around if there's history.
if history:
+9 -3
View File
@@ -169,14 +169,20 @@ def super_len(o):
def get_netrc_auth(url, raise_errors=False):
"""Returns the Requests tuple auth for a given url from netrc."""
netrc_file = os.environ.get('NETRC')
if netrc_file is not None:
netrc_locations = (netrc_file,)
else:
netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES)
try:
from netrc import netrc, NetrcParseError
netrc_path = None
for f in NETRC_FILES:
for f in netrc_locations:
try:
loc = os.path.expanduser('~/{}'.format(f))
loc = os.path.expanduser(f)
except KeyError:
# os.path.expanduser can fail when $HOME is undefined and
# getpwuid fails. See https://bugs.python.org/issue20164 &
@@ -212,7 +218,7 @@ def get_netrc_auth(url, raise_errors=False):
if raise_errors:
raise
# AppEngine hackiness.
# App Engine hackiness.
except (ImportError, AttributeError):
pass
+1 -1
View File
@@ -10,7 +10,7 @@ from .models.lockfile import Lockfile
from .models.pipfile import Pipfile
from .models.requirements import Requirement
__version__ = "1.5.14"
__version__ = "1.5.16"
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -6,7 +6,7 @@ import copy
import functools
import os
from pipenv.vendor import attr
import attr
import packaging.markers
import packaging.version
import pip_shims.shims
+1 -1
View File
@@ -5,7 +5,7 @@ import copy
import itertools
import os
from pipenv.vendor import attr
import attr
import plette.lockfiles
import six
from vistir.compat import FileNotFoundError, JSONDecodeError, Path
+2 -2
View File
@@ -3,7 +3,7 @@ import itertools
import operator
import re
from pipenv.vendor import attr
import attr
import distlib.markers
import packaging.version
import six
@@ -673,7 +673,7 @@ def parse_marker_dict(marker_dict):
def _contains_micro_version(version_string):
return re.search("\d+\.\d+\.\d+", version_string) is not None
return re.search(r"\d+\.\d+\.\d+", version_string) is not None
def format_pyversion(parts):
+1 -1
View File
@@ -9,7 +9,7 @@ import os
import zipfile
from collections import defaultdict
from pipenv.vendor import attr
import attr
import dateutil.parser
import distlib.metadata
import distlib.wheel
+1 -1
View File
@@ -7,7 +7,7 @@ import itertools
import os
import sys
from pipenv.vendor import attr
import attr
import plette.models.base
import plette.pipfiles
import tomlkit
+1 -1
View File
@@ -6,7 +6,7 @@ import collections
import io
import os
from pipenv.vendor import attr
import attr
import packaging.markers
import packaging.utils
import plette
+46 -40
View File
@@ -8,12 +8,10 @@ import os
import sys
from contextlib import contextmanager
from distutils.sysconfig import get_python_lib
from functools import partial
from pipenv.vendor import attr
import attr
import pip_shims
import six
import vistir
from cached_property import cached_property
from packaging.markers import Marker
from packaging.requirements import Requirement as PackagingRequirement
@@ -50,10 +48,6 @@ from ..utils import (
strip_ssh_from_git_uri,
)
from .markers import (
cleanup_pyspecs,
contains_pyversion,
format_pyversion,
get_contained_pyversions,
normalize_marker_str,
)
from .setup_info import (
@@ -67,10 +61,10 @@ from .url import URI
from .utils import (
DIRECT_URL_RE,
HASH_STRING,
URL_RE,
build_vcs_uri,
convert_direct_url_to_url,
create_link,
expand_env_variables,
extras_to_string,
filter_none,
format_requirement,
@@ -82,7 +76,6 @@ from .utils import (
make_install_requirement,
normalize_name,
parse_extras,
read_source,
specs_to_string,
split_markers_from_line,
split_ref_from_uri,
@@ -926,7 +919,7 @@ class Line(object):
if self.is_named:
ireq = pip_shims.shims.install_req_from_line(self.line)
if self.is_file or self.is_remote_url:
ireq.link = self.link
ireq.link = pip_shims.shims.Link(expand_env_variables(self.link.url))
if self.extras and not ireq.extras:
ireq.extras = set(self.extras)
if self.parsed_marker is not None and not ireq.markers:
@@ -1413,43 +1406,51 @@ LinkInfo = collections.namedtuple(
)
@attr.s(slots=True, cmp=True, hash=True)
@attr.s(slots=True, eq=True, order=True, hash=True)
class FileRequirement(object):
"""File requirements for tar.gz installable files or wheels or setup.py
containing directories."""
#: Path to the relevant `setup.py` location
setup_path = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
setup_path = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE]
#: path to hit - without any of the VCS prefixes (like git+ / http+ / etc)
path = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
path = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE]
#: Whether the package is editable
editable = attr.ib(default=False, cmp=True) # type: bool
editable = attr.ib(default=False, eq=True, order=True) # type: bool
#: Extras if applicable
extras = attr.ib(
default=attr.Factory(tuple), cmp=True
default=attr.Factory(tuple), eq=True, order=True
) # type: Tuple[STRING_TYPE, ...]
_uri_scheme = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
_uri_scheme = attr.ib(
default=None, eq=True, order=True
) # type: Optional[STRING_TYPE]
#: URI of the package
uri = attr.ib(cmp=True) # type: Optional[STRING_TYPE]
uri = attr.ib(eq=True, order=True) # type: Optional[STRING_TYPE]
#: Link object representing the package to clone
link = attr.ib(cmp=True) # type: Optional[Link]
link = attr.ib(eq=True, order=True) # type: Optional[Link]
#: PyProject Requirements
pyproject_requires = attr.ib(
factory=tuple, cmp=True
factory=tuple, eq=True, order=True
) # type: Optional[Tuple[STRING_TYPE, ...]]
#: PyProject Build System
pyproject_backend = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
pyproject_backend = attr.ib(
default=None, eq=True, order=True
) # type: Optional[STRING_TYPE]
#: PyProject Path
pyproject_path = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
pyproject_path = attr.ib(
default=None, eq=True, order=True
) # type: Optional[STRING_TYPE]
subdirectory = attr.ib(default=None) # type: Optional[STRING_TYPE]
#: Setup metadata e.g. dependencies
_setup_info = attr.ib(default=None, cmp=True) # type: Optional[SetupInfo]
_has_hashed_name = attr.ib(default=False, cmp=True) # type: bool
_parsed_line = attr.ib(default=None, cmp=False, hash=True) # type: Optional[Line]
_setup_info = attr.ib(default=None, eq=True, order=True) # type: Optional[SetupInfo]
_has_hashed_name = attr.ib(default=False, eq=True, order=True) # type: bool
_parsed_line = attr.ib(
default=None, eq=False, order=False, hash=True
) # type: Optional[Line]
#: Package name
name = attr.ib(cmp=True) # type: Optional[STRING_TYPE]
name = attr.ib(eq=True, order=True) # type: Optional[STRING_TYPE]
#: A :class:`~pkg_resources.Requirement` instance
req = attr.ib(cmp=True) # type: Optional[PackagingRequirement]
req = attr.ib(eq=True, order=True) # type: Optional[PackagingRequirement]
@classmethod
def get_link_from_line(cls, line):
@@ -2140,7 +2141,7 @@ class VCSRequirement(FileRequirement):
if checkout_dir is None:
checkout_dir = self.get_checkout_dir(src_dir=src_dir)
vcsrepo = VCSRepository(
url=self.url,
url=expand_env_variables(self.url),
name=self.name,
ref=self.ref if self.ref else None,
checkout_directory=checkout_dir,
@@ -2373,29 +2374,34 @@ class VCSRequirement(FileRequirement):
return {name: pipfile_dict} # type: ignore
@attr.s(cmp=True, hash=True)
@attr.s(eq=True, order=True, hash=True)
class Requirement(object):
_name = attr.ib(cmp=True) # type: STRING_TYPE
_name = attr.ib(eq=True, order=True) # type: STRING_TYPE
vcs = attr.ib(
default=None, validator=attr.validators.optional(validate_vcs), cmp=True
default=None,
validator=attr.validators.optional(validate_vcs),
eq=True,
order=True,
) # type: Optional[STRING_TYPE]
req = attr.ib(
default=None, cmp=True
default=None, eq=True, order=True
) # type: Optional[Union[VCSRequirement, FileRequirement, NamedRequirement]]
markers = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
markers = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE]
_specifiers = attr.ib(
validator=attr.validators.optional(validate_specifiers), cmp=True
validator=attr.validators.optional(validate_specifiers), eq=True, order=True
) # type: Optional[STRING_TYPE]
index = attr.ib(default=None, cmp=True) # type: Optional[STRING_TYPE]
editable = attr.ib(default=None, cmp=True) # type: Optional[bool]
index = attr.ib(default=None, eq=True, order=True) # type: Optional[STRING_TYPE]
editable = attr.ib(default=None, eq=True, order=True) # type: Optional[bool]
hashes = attr.ib(
factory=frozenset, converter=frozenset, cmp=True
factory=frozenset, converter=frozenset, eq=True, order=True
) # type: FrozenSet[STRING_TYPE]
extras = attr.ib(factory=tuple, cmp=True) # type: Tuple[STRING_TYPE, ...]
abstract_dep = attr.ib(default=None, cmp=False) # type: Optional[AbstractDependency]
_line_instance = attr.ib(default=None, cmp=False) # type: Optional[Line]
extras = attr.ib(factory=tuple, eq=True, order=True) # type: Tuple[STRING_TYPE, ...]
abstract_dep = attr.ib(
default=None, eq=False, order=False
) # type: Optional[AbstractDependency]
_line_instance = attr.ib(default=None, eq=False, order=False) # type: Optional[Line]
_ireq = attr.ib(
default=None, cmp=False
default=None, eq=False, order=False
) # type: Optional[pip_shims.InstallRequirement]
def __hash__(self):
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding=utf-8 -*-
from contextlib import contextmanager
from pipenv.vendor import attr
import attr
import six
from pip_shims.shims import Wheel
+11 -8
View File
@@ -12,7 +12,7 @@ import shutil
import sys
from functools import partial
from pipenv.vendor import attr
import attr
import chardet
import packaging.specifiers
import packaging.utils
@@ -1027,7 +1027,9 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no
constant = ast.Ellipsis
unparsed = item
if isinstance(item, ast.Dict):
unparsed = dict(zip(map(_ensure_hashable, unparse(item.keys)), unparse(item.values)))
unparsed = dict(
zip(map(_ensure_hashable, unparse(item.keys)), unparse(item.values))
)
elif isinstance(item, ast.List):
unparsed = [unparse(el) for el in item.elts]
elif isinstance(item, ast.Tuple):
@@ -1175,7 +1177,7 @@ def ast_unparse(item, initial_mapping=False, analyzer=None, recurse=True): # no
# XXX: Original reference
try:
targets = item.targets # for ast.Assign
except AttributeError: # for ast.AnnAssign
except AttributeError: # for ast.AnnAssign
targets = (item.target,)
if not initial_mapping:
target = unparse(next(iter(targets)), recurse=False)
@@ -1326,9 +1328,9 @@ def run_setup(script_path, egg_base=None):
@attr.s(slots=True, frozen=True)
class BaseRequirement(object):
name = attr.ib(default="", cmp=True) # type: STRING_TYPE
name = attr.ib(default="", eq=True, order=True) # type: STRING_TYPE
requirement = attr.ib(
default=None, cmp=True
default=None, eq=True, order=True
) # type: Optional[PkgResourcesRequirement]
def __str__(self):
@@ -1368,8 +1370,8 @@ class BaseRequirement(object):
@attr.s(slots=True, frozen=True)
class Extra(object):
name = attr.ib(default=None, cmp=True) # type: STRING_TYPE
requirements = attr.ib(factory=frozenset, cmp=True, type=frozenset)
name = attr.ib(default=None, eq=True, order=True) # type: STRING_TYPE
requirements = attr.ib(factory=frozenset, eq=True, order=True, type=frozenset)
def __str__(self):
# type: () -> S
@@ -1933,7 +1935,8 @@ build-backend = "{1}"
if not ireq.source_dir:
build_kwargs = {
"build_dir": kwargs["build_dir"],
"autodelete": False, "parallel_builds": True
"autodelete": False,
"parallel_builds": True,
}
call_function_with_correct_args(build_location_func, **build_kwargs)
ireq.ensure_has_source_dir(kwargs["src_dir"])
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- coding=utf-8 -*-
from __future__ import absolute_import, print_function
from pipenv.vendor import attr
import attr
import pip_shims.shims
from orderedmultidict import omdict
from six.moves.urllib.parse import quote, unquote_plus, unquote as url_unquote
+16
View File
@@ -1028,6 +1028,22 @@ def read_source(path, encoding="utf-8"):
return fp.read()
def expand_env_variables(line):
# type: (AnyStr) -> AnyStr
"""Expand the env vars in a line following pip's standard.
https://pip.pypa.io/en/stable/reference/pip_install/#id10
Matches environment variable-style values in '${MY_VARIABLE_1}' with the
variable name consisting of only uppercase letters, digits or the '_'
"""
def replace_with_env(match):
value = os.getenv(match.group(1))
return value if value else match.group()
return re.sub(r"\$\{([A-Z0-9_]+)\}", replace_with_env, line)
SETUPTOOLS_SHIM = (
"import setuptools, tokenize;__file__=%r;"
"f=getattr(tokenize, 'open', open)(__file__);"
+1 -1
View File
@@ -5,7 +5,7 @@ import importlib
import os
import sys
from pipenv.vendor import attr
import attr
import pip_shims
import six
+1 -1
View File
@@ -11,7 +11,7 @@ __all__ = [
"ResolutionTooDeep",
]
__version__ = "0.3.0"
__version__ = "0.5.2"
from .providers import AbstractProvider, AbstractResolver
View File
+6
View File
@@ -0,0 +1,6 @@
__all__ = ["Sequence"]
try:
from collections.abc import Sequence
except ImportError:
from collections import Sequence
+43 -45
View File
@@ -1,33 +1,37 @@
class AbstractProvider(object):
"""Delegate class to provide requirement interface for the resolver.
"""
"""Delegate class to provide requirement interface for the resolver."""
def identify(self, dependency):
"""Given a dependency, return an identifier for it.
def identify(self, requirement_or_candidate):
"""Given a requirement or candidate, return an identifier for it.
This is used in many places to identify the dependency, e.g. whether
two requirements should have their specifier parts merged, whether
two specifications would conflict with each other (because they the
same name but different versions).
This is used in many places to identify a requirement or candidate,
e.g. whether two requirements should have their specifier parts merged,
whether two candidates would conflict with each other (because they
have same name but different versions).
"""
raise NotImplementedError
def get_preference(self, resolution, candidates, information):
"""Produce a sort key for given specification based on preference.
"""Produce a sort key for given requirement based on preference.
The preference is defined as "I think this requirement should be
resolved first". The lower the return value is, the more preferred
this group of arguments is.
:param resolution: Currently pinned candidate, or `None`.
:param candidates: A list of possible candidates.
:param candidates: An iterable of possible candidates.
:param information: A list of requirement information.
Each information instance is a named tuple with two entries:
The `candidates` iterable's exact type depends on the return type of
`find_matches()`. A sequence is passed-in as-is if possible. If it
returns a callble, the iterator returned by that callable is passed
in here.
Each element in `information` is a named tuple with two entries:
* `requirement` specifies a requirement contributing to the current
candidate list
* `parent` specifies the candidate that provids (dependend on) the
candidate list.
* `parent` specifies the candidate that provides (dependend on) the
requirement, or `None` to indicate a root requirement.
The preference could depend on a various of issues, including (not
@@ -43,28 +47,40 @@ class AbstractProvider(object):
A sortable value should be returned (this will be used as the `key`
parameter of the built-in sorting function). The smaller the value is,
the more preferred this specification is (i.e. the sorting function
the more preferred this requirement is (i.e. the sorting function
is called with `reverse=False`).
"""
raise NotImplementedError
def find_matches(self, requirement):
"""Find all possible candidates that satisfy a requirement.
def find_matches(self, requirements):
"""Find all possible candidates that satisfy the given requirements.
This should try to get candidates based on the requirement's type.
This should try to get candidates based on the requirements' types.
For VCS, local, and archive requirements, the one-and-only match is
returned, and for a "named" requirement, the index(es) should be
consulted to find concrete candidates for this requirement.
The returned candidates should be sorted by reversed preference, e.g.
the most preferred should be LAST. This is done so list-popping can be
as efficient as possible.
The return value should produce candidates ordered by preference; the
most preferred candidate should come first. The return type may be one
of the following:
* A callable that returns an iterator that yields candidates.
* An collection of candidates.
* An iterable of candidates. This will be consumed immediately into a
list of candidates.
:param requirements: A collection of requirements which all of the
returned candidates must match. All requirements are guaranteed to
have the same identifier. The collection is never empty.
"""
raise NotImplementedError
def is_satisfied_by(self, requirement, candidate):
"""Whether the given requirement can be satisfied by a candidate.
The candidate is guarenteed to have been generated from the
requirement.
A boolean should be returned to indicate whether `candidate` is a
viable solution to the requirement.
"""
@@ -80,8 +96,7 @@ class AbstractProvider(object):
class AbstractResolver(object):
"""The thing that performs the actual resolution work.
"""
"""The thing that performs the actual resolution work."""
base_exception = Exception
@@ -92,30 +107,13 @@ class AbstractResolver(object):
def resolve(self, requirements, **kwargs):
"""Take a collection of constraints, spit out the resolution result.
Parameters
----------
requirements : Collection
A collection of constraints
kwargs : optional
Additional keyword arguments that subclasses may accept.
This returns a representation of the final resolution state, with one
guarenteed attribute ``mapping`` that contains resolved candidates as
values. The keys are their respective identifiers.
Raises
------
self.base_exception
Any raised exception is guaranteed to be a subclass of
self.base_exception. The string representation of an exception
should be human readable and provide context for why it occurred.
:param requirements: A collection of constraints.
:param kwargs: Additional keyword arguments that subclasses may accept.
Returns
-------
retval : object
A representation of the final resolution state. It can be any object
with a `mapping` attribute that is a Mapping. Other attributes can
be used to provide resolver-specific information.
The `mapping` attribute MUST be key-value pair is an identifier of a
requirement (as returned by the provider's `identify` method) mapped
to the resolved candidate (chosen from the return value of the
provider's `find_matches` method).
:raises: ``self.base_exception`` or its subclass.
"""
raise NotImplementedError
+13 -12
View File
@@ -1,10 +1,8 @@
class BaseReporter(object):
"""Delegate class to provider progress reporting for the resolver.
"""
"""Delegate class to provider progress reporting for the resolver."""
def starting(self):
"""Called before the resolution actually starts.
"""
"""Called before the resolution actually starts."""
def starting_round(self, index):
"""Called before each round of resolution starts.
@@ -20,17 +18,20 @@ class BaseReporter(object):
"""
def ending(self, state):
"""Called before the resolution ends successfully.
"""
"""Called before the resolution ends successfully."""
def adding_requirement(self, requirement):
"""Called when the resolver adds a new requirement into the resolve criteria.
def adding_requirement(self, requirement, parent):
"""Called when adding a new requirement into the resolve criteria.
:param requirement: The additional requirement to be applied to filter
the available candidaites.
:param parent: The candidate that requires ``requirement`` as a
dependency, or None if ``requirement`` is one of the root
requirements passed in from ``Resolver.resolve()``.
"""
def backtracking(self, candidate):
"""Called when the resolver rejects a candidate during backtracking.
"""
"""Called when rejecting a candidate during backtracking."""
def pinning(self, candidate):
"""Called when adding a candidate to the potential solution.
"""
"""Called when adding a candidate to the potential solution."""
+53 -52
View File
@@ -1,7 +1,7 @@
import collections
from .providers import AbstractResolver
from .structs import DirectedGraph
from .structs import DirectedGraph, build_iter_view
RequirementInformation = collections.namedtuple(
@@ -68,22 +68,18 @@ class Criterion(object):
def __repr__(self):
requirements = ", ".join(
"{!r} from {!r}".format(req, parent)
"({!r}, via={!r})".format(req, parent)
for req, parent in self.information
)
return "<Criterion {}>".format(requirements)
return "Criterion({})".format(requirements)
@classmethod
def from_requirement(cls, provider, requirement, parent):
"""Build an instance from a requirement.
"""
candidates = provider.find_matches(requirement)
criterion = cls(
candidates=candidates,
information=[RequirementInformation(requirement, parent)],
incompatibilities=[],
)
if not candidates:
"""Build an instance from a requirement."""
cands = build_iter_view(provider.find_matches([requirement]))
infos = [RequirementInformation(requirement, parent)]
criterion = cls(cands, infos, incompatibilities=[])
if not cands:
raise RequirementsConflicted(criterion)
return criterion
@@ -94,17 +90,12 @@ class Criterion(object):
return (i.parent for i in self.information)
def merged_with(self, provider, requirement, parent):
"""Build a new instance from this and a new requirement.
"""
"""Build a new instance from this and a new requirement."""
infos = list(self.information)
infos.append(RequirementInformation(requirement, parent))
candidates = [
c
for c in self.candidates
if provider.is_satisfied_by(requirement, c)
]
criterion = type(self)(candidates, infos, list(self.incompatibilities))
if not candidates:
cands = build_iter_view(provider.find_matches([r for r, _ in infos]))
criterion = type(self)(cands, infos, list(self.incompatibilities))
if not cands:
raise RequirementsConflicted(criterion)
return criterion
@@ -113,13 +104,12 @@ class Criterion(object):
Returns the new instance, or None if we still have no valid candidates.
"""
cands = self.candidates.excluding(candidate)
if not cands:
return None
incompats = list(self.incompatibilities)
incompats.append(candidate)
candidates = [c for c in self.candidates if c != candidate]
if not candidates:
return None
criterion = type(self)(candidates, list(self.information), incompats)
return criterion
return type(self)(cands, list(self.information), incompats)
class ResolutionError(ResolverException):
@@ -174,12 +164,13 @@ class Resolution(object):
state = State(mapping=collections.OrderedDict(), criteria={})
else:
state = State(
mapping=base.mapping.copy(), criteria=base.criteria.copy(),
mapping=base.mapping.copy(),
criteria=base.criteria.copy(),
)
self._states.append(state)
def _merge_into_criterion(self, requirement, parent):
self._r.adding_requirement(requirement)
self._r.adding_requirement(requirement, parent)
name = self._p.identify(requirement)
try:
crit = self.state.criteria[name]
@@ -191,12 +182,10 @@ class Resolution(object):
def _get_criterion_item_preference(self, item):
name, criterion = item
try:
pinned = self.state.mapping[name]
except KeyError:
pinned = None
return self._p.get_preference(
pinned, criterion.candidates, criterion.information,
self.state.mapping.get(name),
criterion.candidates.for_preference(),
criterion.information,
)
def _is_current_pin_satisfying(self, name, criterion):
@@ -218,13 +207,24 @@ class Resolution(object):
def _attempt_to_pin_criterion(self, name, criterion):
causes = []
for candidate in reversed(criterion.candidates):
for candidate in criterion.candidates:
try:
criteria = self._get_criteria_to_update(candidate)
except RequirementsConflicted as e:
causes.append(e.criterion)
continue
# Check the newly-pinned candidate actually works. This should
# always pass under normal circumstances, but in the case of a
# faulty provider, we will raise an error to notify the implementer
# to fix find_matches() and/or is_satisfied_by().
satisfied = all(
self._p.is_satisfied_by(r, candidate)
for r in criterion.iter_requirement()
)
if not satisfied:
raise InconsistentCandidate(candidate, criterion)
# Put newly-pinned candidate at the end. This is essential because
# backtracking looks at this mapping to get the last pin.
self._r.pinning(candidate)
@@ -232,13 +232,6 @@ class Resolution(object):
self.state.mapping[name] = candidate
self.state.criteria.update(criteria)
# Check the newly-pinned candidate actually works. This should
# always pass under normal circumstances, but in the case of a
# faulty provider, we will raise an error to notify the implementer
# to fix find_matches() and/or is_satisfied_by().
if not self._is_current_pin_satisfying(name, criterion):
raise InconsistentCandidate(candidate, criterion)
return []
# All candidates tried, nothing works. This criterion is a dead
@@ -246,23 +239,32 @@ class Resolution(object):
return causes
def _backtrack(self):
# We need at least 3 states here:
# (a) One known not working, to drop.
# (b) One to backtrack to.
# (c) One to restore state (b) to its state prior to candidate-pinning,
# so we can pin another one instead.
while len(self._states) >= 3:
del self._states[-1]
# Drop the current state, it's known not to work.
del self._states[-1]
# Retract the last candidate pin, and create a new (b).
name, candidate = self._states.pop().mapping.popitem()
# We need at least 2 states here:
# (a) One to backtrack to.
# (b) One to restore state (a) to its state prior to candidate-pinning,
# so we can pin another one instead.
while len(self._states) >= 2:
# Retract the last candidate pin.
prev_state = self._states.pop()
try:
name, candidate = prev_state.mapping.popitem()
except KeyError:
continue
self._r.backtracking(candidate)
# Create a new state to work on, with the newly known not-working
# candidate excluded.
self._push_new_state()
# Mark the retracted candidate as incompatible.
criterion = self.state.criteria[name].excluded_of(candidate)
if criterion is None:
# This state still does not work. Try the still previous state.
del self._states[-1]
continue
self.state.criteria[name] = criterion
@@ -376,8 +378,7 @@ def _build_result(state):
class Resolver(AbstractResolver):
"""The thing that performs the actual resolution work.
"""
"""The thing that performs the actual resolution work."""
base_exception = ResolverException
+83 -8
View File
@@ -1,6 +1,8 @@
from .compat import collections_abc
class DirectedGraph(object):
"""A graph structure with directed edges.
"""
"""A graph structure with directed edges."""
def __init__(self):
self._vertices = set()
@@ -17,8 +19,7 @@ class DirectedGraph(object):
return key in self._vertices
def copy(self):
"""Return a shallow copy of this graph.
"""
"""Return a shallow copy of this graph."""
other = DirectedGraph()
other._vertices = set(self._vertices)
other._forwards = {k: set(v) for k, v in self._forwards.items()}
@@ -26,8 +27,7 @@ class DirectedGraph(object):
return other
def add(self, key):
"""Add a new vertex to the graph.
"""
"""Add a new vertex to the graph."""
if key in self._vertices:
raise ValueError("vertex exists")
self._vertices.add(key)
@@ -35,8 +35,7 @@ class DirectedGraph(object):
self._backwards[key] = set()
def remove(self, key):
"""Remove a vertex from the graph, disconnecting all edges from/to it.
"""
"""Remove a vertex from the graph, disconnecting all edges from/to it."""
self._vertices.remove(key)
for f in self._forwards.pop(key):
self._backwards[f].remove(key)
@@ -66,3 +65,79 @@ class DirectedGraph(object):
def iter_parents(self, key):
return iter(self._backwards[key])
class _FactoryIterableView(object):
"""Wrap an iterator factory returned by `find_matches()`.
Calling `iter()` on this class would invoke the underlying iterator
factory, making it a "collection with ordering" that can be iterated
through multiple times, but lacks random access methods presented in
built-in Python sequence types.
"""
def __init__(self, factory):
self._factory = factory
def __bool__(self):
try:
next(self._factory())
except StopIteration:
return False
return True
__nonzero__ = __bool__ # XXX: Python 2.
def __iter__(self):
return self._factory()
def for_preference(self):
"""Provide an candidate iterable for `get_preference()`"""
return self._factory()
def excluding(self, candidate):
"""Create a new `Candidates` instance excluding `candidate`."""
def factory():
return (c for c in self._factory() if c != candidate)
return type(self)(factory)
class _SequenceIterableView(object):
"""Wrap an iterable returned by find_matches().
This is essentially just a proxy to the underlying sequence that provides
the same interface as `_FactoryIterableView`.
"""
def __init__(self, sequence):
self._sequence = sequence
def __bool__(self):
return bool(self._sequence)
__nonzero__ = __bool__ # XXX: Python 2.
def __iter__(self):
return iter(self._sequence)
def __len__(self):
return len(self._sequence)
def for_preference(self):
"""Provide an candidate iterable for `get_preference()`"""
return self._sequence
def excluding(self, candidate):
"""Create a new instance excluding `candidate`."""
return type(self)([c for c in self._sequence if c != candidate])
def build_iter_view(matches):
"""Build an iterable view from the value returned by `find_matches()`."""
if callable(matches):
return _FactoryIterableView(matches)
if not isinstance(matches, collections_abc.Sequence):
matches = list(matches)
return _SequenceIterableView(matches)
+851 -341
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -29,7 +29,7 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.14.0"
__version__ = "1.15.0"
# Useful for very coarse version differentiation.
@@ -890,12 +890,11 @@ def ensure_binary(s, encoding='utf-8', errors='strict'):
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
if isinstance(s, binary_type):
return s
if isinstance(s, text_type):
return s.encode(encoding, errors)
elif isinstance(s, binary_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
@@ -909,12 +908,15 @@ def ensure_str(s, encoding='utf-8', errors='strict'):
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
# Optimization: Fast return for the common case.
if type(s) is str:
return s
if PY2 and isinstance(s, text_type):
s = s.encode(encoding, errors)
return s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
s = s.decode(encoding, errors)
return s.decode(encoding, errors)
elif not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
return s

Some files were not shown because too many files have changed in this diff Show More