From 8df5dc154f06f17483723231ae633cf62ecdad9a Mon Sep 17 00:00:00 2001 From: JackDandy Date: Thu, 12 Nov 2020 11:46:00 +0000 Subject: [PATCH 01/36] Change abbreviate long titles under menu tab. --- CHANGES.md | 7 ++++++- gui/slick/interfaces/default/inc_top.tmpl | 3 ++- sickbeard/show_name_helpers.py | 12 ++++++++++++ sickbeard/webserve.py | 3 ++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b1f3866..da41974 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,9 @@ -### 0.23.0 (2020-11-11 13:30:00 UTC) +### 0.24.0 (202x-xx-xx xx:xx:00 UTC) + +* Change abbreviate long titles under menu tab + + +### 0.23.0 (2020-11-11 13:30:00 UTC) * Change improve search performance for backlog, manual, failed, and proper * Add overview of the last release age/date at each newznab provider to History/Layout "Connect fails" diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl index 443d30d..44ef7d6 100644 --- a/gui/slick/interfaces/default/inc_top.tmpl +++ b/gui/slick/interfaces/default/inc_top.tmpl @@ -3,6 +3,7 @@ #import urllib #from sickbeard.common import Quality, SNATCHED_ANY, DOWNLOADED, ARCHIVED, FAILED #from sickbeard.helpers import anon_url +#from sickbeard.show_name_helpers import abbr_showname <% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# <% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# #slurp @@ -196,7 +197,7 @@ #else #for item in $added_last #if $hasattr($item, 'tvid_prodid') -
  • $item.name
  • +
  • $abbr_showname($item.name)
  • #end if #end for #end if diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py index 98e19aa..7726623 100644 --- a/sickbeard/show_name_helpers.py +++ b/sickbeard/show_name_helpers.py @@ -458,3 +458,15 @@ def determineReleaseName(dir_name=None, nzb_name=None): return folder return None + + +def abbr_showname(name): + # type: (AnyStr) -> AnyStr + result = name + for cur_from, cur_to in ( + (r'^Star Trek\s*:\s*', r'ST: '), (r'^The Walking Dead\s*:\s*', r'TWD: '), + ): + result = re.sub('(?i)%s' % cur_from, cur_to, result) + if name != result: + break + return result diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 6b6abc7..b66e8d6 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -62,6 +62,7 @@ from .scene_numbering import get_scene_absolute_numbering_for_show, get_scene_nu get_xem_absolute_numbering_for_show, get_xem_numbering_for_show, set_scene_numbering_helper from .search_backlog import FORCED_BACKLOG from .sgdatetime import SGDatetime, timestamp_near +from .show_name_helpers import abbr_showname from .show_updater import clean_ignore_require_words from .trakt_helpers import build_config, trakt_collection_remove_account @@ -6163,10 +6164,10 @@ class History(MainHandler): for item in history_compact: if item.get('tvid_prodid') not in dedupe: dedupe.add(item.get('tvid_prodid')) + item['show_name'] = abbr_showname(item['show_name']) result += [item] if limit == len(result): break - return result @classmethod From f81dfb5d6dc3a658ccc2ab8d45bc7d9f09e765d5 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Thu, 12 Nov 2020 12:42:32 +0000 Subject: [PATCH 02/36] =?UTF-8?q?Update=20Requests=20library=202.24.0=20(2?= =?UTF-8?q?f70990)=20=E2=86=92=202.25.0=20(03957eb).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 1 + lib/requests/__init__.py | 4 ++-- lib/requests/__version__.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index da41974..3128315 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ ### 0.24.0 (202x-xx-xx xx:xx:00 UTC) * Change abbreviate long titles under menu tab +* Update Requests library 2.24.0 (2f70990) to 2.25.0 (03957eb) ### 0.23.0 (2020-11-11 13:30:00 UTC) diff --git a/lib/requests/__init__.py b/lib/requests/__init__.py index d0b51b2..d1db6fe 100644 --- a/lib/requests/__init__.py +++ b/lib/requests/__init__.py @@ -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] diff --git a/lib/requests/__version__.py b/lib/requests/__version__.py index 531e26c..7108520 100644 --- a/lib/requests/__version__.py +++ b/lib/requests/__version__.py @@ -5,8 +5,8 @@ __title__ = 'requests' __description__ = 'Python HTTP for Humans.' __url__ = 'https://requests.readthedocs.io' -__version__ = '2.24.0' -__build__ = 0x022400 +__version__ = '2.25.0' +__build__ = 0x022500 __author__ = 'Kenneth Reitz' __author_email__ = 'me@kennethreitz.org' __license__ = 'Apache 2.0' From 35de8a7566d65c2c1e0753f73fd75295f4ed2258 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Thu, 12 Nov 2020 12:49:57 +0000 Subject: [PATCH 03/36] =?UTF-8?q?Update=20urllib3=201.25.11=20(00f1769)=20?= =?UTF-8?q?=E2=86=92=201.26.1=20(7675532).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 1 + lib/urllib3/_version.py | 2 +- lib/urllib3/connection.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3128315..d96bd12 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ * Change abbreviate long titles under menu tab * Update Requests library 2.24.0 (2f70990) to 2.25.0 (03957eb) +* Update urllib3 1.25.11 (00f1769) to 1.26.1 (7675532) ### 0.23.0 (2020-11-11 13:30:00 UTC) diff --git a/lib/urllib3/_version.py b/lib/urllib3/_version.py index 5a0ec53..cd4e7b0 100644 --- a/lib/urllib3/_version.py +++ b/lib/urllib3/_version.py @@ -1,2 +1,2 @@ # This file is protected via CODEOWNERS -__version__ = "1.25.11" +__version__ = "1.26.1" diff --git a/lib/urllib3/connection.py b/lib/urllib3/connection.py index 5248741..2a5a3f8 100644 --- a/lib/urllib3/connection.py +++ b/lib/urllib3/connection.py @@ -228,7 +228,7 @@ class HTTPConnection(_HTTPConnection, object): else: # Avoid modifying the headers passed into .request() headers = headers.copy() - if "user-agent" not in (k.lower() for k in headers): + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): headers["User-Agent"] = _get_default_user_agent() super(HTTPConnection, self).request(method, url, body=body, headers=headers) From fcc24816cd24ca49c78d9e543b0a484fb1e0d128 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Thu, 12 Nov 2020 14:48:08 +0000 Subject: [PATCH 04/36] =?UTF-8?q?Update=20attr=2020.2.0=20(4f74fba)=20?= =?UTF-8?q?=E2=86=92=2020.3.0=20(f3762ba).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES.md | 1 + lib/attr/__init__.py | 2 +- lib/attr/__init__.pyi | 10 ++++ lib/attr/_compat.py | 4 +- lib/attr/_funcs.py | 76 ++++++++++++++++++++---- lib/attr/_make.py | 158 ++++++++++++++++++++++++++++++++++++++++++-------- lib/attr/_next_gen.py | 2 + 7 files changed, 215 insertions(+), 38 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d96bd12..888f029 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ ### 0.24.0 (202x-xx-xx xx:xx:00 UTC) * Change abbreviate long titles under menu tab +* Update attr 20.2.0 (4f74fba) to 20.3.0 (f3762ba) * Update Requests library 2.24.0 (2f70990) to 2.25.0 (03957eb) * Update urllib3 1.25.11 (00f1769) to 1.26.1 (7675532) diff --git a/lib/attr/__init__.py b/lib/attr/__init__.py index 7a79e57..bf329ca 100644 --- a/lib/attr/__init__.py +++ b/lib/attr/__init__.py @@ -21,7 +21,7 @@ from ._make import ( from ._version_info import VersionInfo -__version__ = "20.2.0" +__version__ = "20.3.0" __version_info__ = VersionInfo._from_version_string(__version__) __title__ = "attrs" diff --git a/lib/attr/__init__.pyi b/lib/attr/__init__.pyi index 0869914..442d6e7 100644 --- a/lib/attr/__init__.pyi +++ b/lib/attr/__init__.pyi @@ -46,6 +46,7 @@ _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. @@ -272,8 +273,10 @@ def attrs( 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( @@ -295,8 +298,10 @@ def attrs( 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( @@ -319,6 +324,7 @@ def define( auto_detect: bool = ..., getstate_setstate: Optional[bool] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., ) -> _C: ... @overload def define( @@ -341,6 +347,7 @@ def define( auto_detect: bool = ..., getstate_setstate: Optional[bool] = ..., on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., ) -> Callable[[_C], _C]: ... mutable = define @@ -381,7 +388,9 @@ 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 -- @@ -397,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 diff --git a/lib/attr/_compat.py b/lib/attr/_compat.py index bed5b13..b0ead6e 100644 --- a/lib/attr/_compat.py +++ b/lib/attr/_compat.py @@ -91,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. @@ -132,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,)) diff --git a/lib/attr/_funcs.py b/lib/attr/_funcs.py index ca92f9f..e6c930c 100644 --- a/lib/attr/_funcs.py +++ b/lib/attr/_funcs.py @@ -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) diff --git a/lib/attr/_make.py b/lib/attr/_make.py index 0fbbd7c..49484f9 100644 --- a/lib/attr/_make.py +++ b/lib/attr/_make.py @@ -12,6 +12,7 @@ from operator import itemgetter from . import _config, setters from ._compat import ( PY2, + PYPY, isclass, iteritems, metadata_proxy, @@ -223,6 +224,7 @@ def attrib( .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. .. versionadded:: 19.2.0 *eq* and *order* .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 """ eq, order = _determine_eq_order(cmp, eq, order, True) @@ -373,7 +375,7 @@ def _collect_base_attrs(cls, taken_attr_names): if a.inherited or a.name in taken_attr_names: continue - a = a._assoc(inherited=True) + a = a.evolve(inherited=True) base_attrs.append(a) base_attr_map[a.name] = base_cls @@ -411,7 +413,7 @@ def _collect_base_attrs_broken(cls, taken_attr_names): if a.name in taken_attr_names: continue - a = a._assoc(inherited=True) + a = a.evolve(inherited=True) taken_attr_names.add(a.name) base_attrs.append(a) base_attr_map[a.name] = base_cls @@ -419,7 +421,9 @@ def _collect_base_attrs_broken(cls, taken_attr_names): return base_attrs, base_attr_map -def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro): +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +): """ Transform all `_CountingAttr`s on a class into `Attribute`s. @@ -451,6 +455,7 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro): continue annot_names.add(attr_name) a = cd.get(attr_name, NOTHING) + if not isinstance(a, _CountingAttr): if a is NOTHING: a = attrib() @@ -498,8 +503,8 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro): AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) if kw_only: - own_attrs = [a._assoc(kw_only=True) for a in own_attrs] - base_attrs = [a._assoc(kw_only=True) for a in base_attrs] + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] attrs = AttrsClass(base_attrs + own_attrs) @@ -518,14 +523,34 @@ def _transform_attrs(cls, these, auto_attribs, kw_only, collect_by_mro): if had_default is False and a.default is not NOTHING: had_default = True + if field_transformer is not None: + attrs = field_transformer(cls, attrs) return _Attributes((attrs, base_attrs, base_attr_map)) -def _frozen_setattrs(self, name, value): - """ - Attached to frozen classes as __setattr__. - """ - raise FrozenInstanceError() +if PYPY: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() + + +else: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() def _frozen_delattrs(self, name): @@ -574,9 +599,15 @@ class _ClassBuilder(object): collect_by_mro, on_setattr, has_custom_setattr, + field_transformer, ): attrs, base_attrs, base_map = _transform_attrs( - cls, these, auto_attribs, kw_only, collect_by_mro + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, ) self._cls = cls @@ -1001,6 +1032,7 @@ def attrs( collect_by_mro=False, getstate_setstate=None, on_setattr=None, + field_transformer=None, ): r""" A class decorator that adds `dunder @@ -1093,12 +1125,14 @@ def attrs( argument name. If a ``__attrs_post_init__`` method exists on the class, it will be called after the class is fully initialized. :param bool slots: Create a `slotted class ` that's more - memory-efficient. + memory-efficient. Slotted classes are generally superior to the default + dict classes, but have some gotchas you should know about, so we + encourage you to read the `glossary entry `. :param bool frozen: Make instances immutable after initialization. If someone attempts to modify a frozen instance, `attr.exceptions.FrozenInstanceError` is raised. - Please note: + .. note:: 1. This is achieved by installing a custom ``__setattr__`` method on your class, so you can't implement your own. @@ -1184,7 +1218,7 @@ def attrs( :param on_setattr: A callable that is run whenever the user attempts to set an attribute (either by assignment like ``i.x = 42`` or by using - `setattr` like ``setattr(i, "x", 42)``). It receives the same argument + `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments as validators: the instance, the attribute that is being modified, and the new value. @@ -1194,6 +1228,11 @@ def attrs( If a list of callables is passed, they're automatically wrapped in an `attr.setters.pipe`. + :param Optional[callable] field_transformer: + A function that is called with the original class object and all + fields right before ``attrs`` finalizes the class. You can use + this, e.g., to automatically add converters or validators to + fields based on their types. See `transform-fields` for more details. .. versionadded:: 16.0.0 *slots* .. versionadded:: 16.1.0 *frozen* @@ -1223,6 +1262,7 @@ def attrs( .. versionadded:: 20.1.0 *collect_by_mro* .. versionadded:: 20.1.0 *getstate_setstate* .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* """ if auto_detect and PY2: raise PythonTooOldError( @@ -1269,6 +1309,7 @@ def attrs( collect_by_mro, on_setattr, has_own_setattr, + field_transformer, ) if _determine_whether_to_implement( cls, repr, auto_detect, ("__repr__",) @@ -1903,6 +1944,63 @@ def _assign_with_converter(attr_name, value_var, has_on_setattr): ) +if PY2: + + def _unpack_kw_only_py2(attr_name, default=None): + """ + Unpack *attr_name* from _kw_only dict. + """ + if default is not None: + arg_default = ", %s" % default + else: + arg_default = "" + return "%s = _kw_only.pop('%s'%s)" % ( + attr_name, + attr_name, + arg_default, + ) + + def _unpack_kw_only_lines_py2(kw_only_args): + """ + Unpack all *kw_only_args* from _kw_only dict and handle errors. + + Given a list of strings "{attr_name}" and "{attr_name}={default}" + generates list of lines of code that pop attrs from _kw_only dict and + raise TypeError similar to builtin if required attr is missing or + extra key is passed. + + >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) + try: + a = _kw_only.pop('a') + b = _kw_only.pop('b', 42) + except KeyError as _key_error: + raise TypeError( + ... + if _kw_only: + raise TypeError( + ... + """ + lines = ["try:"] + lines.extend( + " " + _unpack_kw_only_py2(*arg.split("=")) + for arg in kw_only_args + ) + lines += """\ +except KeyError as _key_error: + raise TypeError( + '__init__() missing required keyword-only argument: %s' % _key_error + ) +if _kw_only: + raise TypeError( + '__init__() got an unexpected keyword argument %r' + % next(iter(_kw_only)) + ) +""".split( + "\n" + ) + return lines + + def _attrs_to_init_script( attrs, frozen, @@ -2157,14 +2255,14 @@ def _attrs_to_init_script( args = ", ".join(args) if kw_only_args: if PY2: - raise PythonTooOldError( - "Keyword-only arguments only work on Python 3 and later." - ) + lines = _unpack_kw_only_lines_py2(kw_only_args) + lines - args += "{leading_comma}*, {kw_only_args}".format( - leading_comma=", " if args else "", - kw_only_args=", ".join(kw_only_args), - ) + args += "%s**_kw_only" % (", " if args else "",) # leading comma + else: + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) return ( """\ def __init__(self, {args}): @@ -2181,6 +2279,13 @@ class Attribute(object): """ *Read-only* representation of an attribute. + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The *field transformer* hook receives a list of them. + :attribute name: The name of the attribute. :attribute inherited: Whether or not that attribute has been inherited from a base class. @@ -2303,10 +2408,17 @@ class Attribute(object): return self.eq and self.order - # Don't use attr.assoc since fields(Attribute) doesn't work - def _assoc(self, **changes): + # Don't use attr.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): """ Copy *self* and apply *changes*. + + This works similarly to `attr.evolve` but that function does not work + with ``Attribute``. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 """ new = copy.copy(self) diff --git a/lib/attr/_next_gen.py b/lib/attr/_next_gen.py index b5ff60e..2b5565c 100644 --- a/lib/attr/_next_gen.py +++ b/lib/attr/_next_gen.py @@ -33,6 +33,7 @@ def define( auto_detect=True, getstate_setstate=None, on_setattr=None, + field_transformer=None, ): r""" The only behavioral differences are the handling of the *auto_attribs* @@ -72,6 +73,7 @@ def define( collect_by_mro=True, getstate_setstate=getstate_setstate, on_setattr=on_setattr, + field_transformer=field_transformer, ) def wrap(cls): From 4902910b97439144415cb4756ebe9d1e9c7a1076 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Thu, 12 Nov 2020 14:55:23 +0000 Subject: [PATCH 05/36] =?UTF-8?q?Update=20diskcache=5Fpy3=205.0.1=20(9670f?= =?UTF-8?q?bb)=20=E2=86=92=205.1.0=20(40ce0de).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update diskcache_py2 4.1.0 (b0451e0) from 5.1.0 (40ce0de). --- CHANGES.md | 2 ++ lib/diskcache_py2/__init__.py | 4 ++-- lib/diskcache_py2/fanout.py | 38 ++++++++++++++++++++++++++++++++++++++ lib/diskcache_py3/__init__.py | 4 ++-- lib/diskcache_py3/fanout.py | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 888f029..a58c5fc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,8 @@ * Change abbreviate long titles under menu tab * Update attr 20.2.0 (4f74fba) to 20.3.0 (f3762ba) +* Update diskcache_py3 5.0.1 (9670fbb) to 5.1.0 (40ce0de) +* Update diskcache_py2 4.1.0 (b0451e0) from 5.1.0 (40ce0de) * Update Requests library 2.24.0 (2f70990) to 2.25.0 (03957eb) * Update urllib3 1.25.11 (00f1769) to 1.26.1 (7675532) diff --git a/lib/diskcache_py2/__init__.py b/lib/diskcache_py2/__init__.py index a98cfee..e4d747b 100644 --- a/lib/diskcache_py2/__init__.py +++ b/lib/diskcache_py2/__init__.py @@ -46,8 +46,8 @@ except Exception: # pylint: disable=broad-except pass __title__ = 'diskcache' -__version__ = '5.0.3' -__build__ = 0x050003 +__version__ = '5.1.0' +__build__ = 0x050100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2020 Grant Jenks' diff --git a/lib/diskcache_py2/fanout.py b/lib/diskcache_py2/fanout.py index fcd1861..15716b7 100644 --- a/lib/diskcache_py2/fanout.py +++ b/lib/diskcache_py2/fanout.py @@ -1,5 +1,6 @@ "Fanout cache automatically shards keys and values." +import contextlib as cl import itertools as it import operator import os.path as op @@ -69,9 +70,46 @@ class FanoutCache(object): def __getattr__(self, name): + safe_names = {'timeout', 'disk'} + valid_name = name in DEFAULT_SETTINGS or name in safe_names + assert valid_name, 'cannot access {} in cache shard'.format(name) return getattr(self._shards[0], name) + @cl.contextmanager + def transact(self, retry=True): + """Context manager to perform a transaction by locking the cache. + + While the cache is locked, no other write operation is permitted. + Transactions should therefore be as short as possible. Read and write + operations performed in a transaction are atomic. Read operations may + occur concurrent to a transaction. + + Transactions may be nested and may not be shared between threads. + + Blocks until transactions are held on all cache shards by retrying as + necessary. + + >>> cache = FanoutCache() + >>> with cache.transact(): # Atomically increment two keys. + ... _ = cache.incr('total', 123.4) + ... _ = cache.incr('count', 1) + >>> with cache.transact(): # Atomically calculate average. + ... average = cache['total'] / cache['count'] + >>> average + 123.4 + + :return: context manager for use in `with` statement + + """ + assert retry, 'retry must be True in FanoutCache' + with cl.ExitStack() as stack: + for shard in self._shards: + shard_transaction = shard.transact(retry=True) + stack.enter_context(shard_transaction) + yield + + def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache. diff --git a/lib/diskcache_py3/__init__.py b/lib/diskcache_py3/__init__.py index a98cfee..e4d747b 100644 --- a/lib/diskcache_py3/__init__.py +++ b/lib/diskcache_py3/__init__.py @@ -46,8 +46,8 @@ except Exception: # pylint: disable=broad-except pass __title__ = 'diskcache' -__version__ = '5.0.3' -__build__ = 0x050003 +__version__ = '5.1.0' +__build__ = 0x050100 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2020 Grant Jenks' diff --git a/lib/diskcache_py3/fanout.py b/lib/diskcache_py3/fanout.py index a579a17..7e227c8 100644 --- a/lib/diskcache_py3/fanout.py +++ b/lib/diskcache_py3/fanout.py @@ -1,5 +1,6 @@ "Fanout cache automatically shards keys and values." +import contextlib as cl import functools import itertools as it import operator @@ -58,9 +59,46 @@ class FanoutCache(object): def __getattr__(self, name): + safe_names = {'timeout', 'disk'} + valid_name = name in DEFAULT_SETTINGS or name in safe_names + assert valid_name, 'cannot access {} in cache shard'.format(name) return getattr(self._shards[0], name) + @cl.contextmanager + def transact(self, retry=True): + """Context manager to perform a transaction by locking the cache. + + While the cache is locked, no other write operation is permitted. + Transactions should therefore be as short as possible. Read and write + operations performed in a transaction are atomic. Read operations may + occur concurrent to a transaction. + + Transactions may be nested and may not be shared between threads. + + Blocks until transactions are held on all cache shards by retrying as + necessary. + + >>> cache = FanoutCache() + >>> with cache.transact(): # Atomically increment two keys. + ... _ = cache.incr('total', 123.4) + ... _ = cache.incr('count', 1) + >>> with cache.transact(): # Atomically calculate average. + ... average = cache['total'] / cache['count'] + >>> average + 123.4 + + :return: context manager for use in `with` statement + + """ + assert retry, 'retry must be True in FanoutCache' + with cl.ExitStack() as stack: + for shard in self._shards: + shard_transaction = shard.transact(retry=True) + stack.enter_context(shard_transaction) + yield + + def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache. From 0e70b2eb437f511e4991ec55b52895c0284e6bc9 Mon Sep 17 00:00:00 2001 From: JackDandy Date: Fri, 13 Nov 2020 13:38:57 +0000 Subject: [PATCH 06/36] Change initialise Manage/Media Process folder and method from Config/Media Process when no previous values are stored. Change remember Manage/Media Process folder and method when button 'Process' is used. Change code style, use `cur_` in `for` loop vars. Tidy ups, and address code warnings. --- CHANGES.md | 2 ++ gui/slick/interfaces/default/home_postprocess.tmpl | 16 ++++++------ sickbeard/__init__.py | 11 +++++++- sickbeard/db.py | 12 ++++----- sickbeard/helpers.py | 4 +-- sickbeard/providers/generic.py | 1 + sickbeard/webserve.py | 29 ++++++++++++++-------- 7 files changed, 48 insertions(+), 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a58c5fc..9cc631e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ ### 0.24.0 (202x-xx-xx xx:xx:00 UTC) +* Change initialise Manage/Media Process folder and method from Config/Media Process when no previous values are stored +* Change remember Manage/Media Process folder and method when button 'Process' is used * Change abbreviate long titles under menu tab * Update attr 20.2.0 (4f74fba) to 20.3.0 (f3762ba) * Update diskcache_py3 5.0.1 (9670fbb) to 5.1.0 (40ce0de) diff --git a/gui/slick/interfaces/default/home_postprocess.tmpl b/gui/slick/interfaces/default/home_postprocess.tmpl index 95aef08..29bd02f 100644 --- a/gui/slick/interfaces/default/home_postprocess.tmpl +++ b/gui/slick/interfaces/default/home_postprocess.tmpl @@ -1,4 +1,6 @@ #import sickbeard +<% def sg_var(varname, default=False): return getattr(sickbeard, varname, default) %>#slurp# +<% def sg_str(varname, default=''): return getattr(sickbeard, varname, default) %>#slurp# ## #set global $header = 'Process Media' #set global $title = $header @@ -6,7 +8,7 @@ #set global $sbPath = '../..' ## #import os.path -#include $os.path.join($sickbeard.PROG_DIR, 'gui/slick/interfaces/default/inc_top.tmpl') +#include $os.path.join($sg_var('PROG_DIR'), 'gui/slick/interfaces/default/inc_top.tmpl') #if $varExists('header')

    $header

    @@ -26,7 +28,7 @@ @@ -39,12 +41,12 @@ @@ -71,7 +73,7 @@ -#if $sickbeard.USE_FAILED_DOWNLOADS: +#if $sg_var('USE_FAILED_DOWNLOADS'):