pysubtypes
Advanced tools
+1
-1
| Metadata-Version: 2.1 | ||
| Name: pysubtypes | ||
| Version: 0.3.17 | ||
| Version: 0.3.18 | ||
| Summary: Provides subclasses for common python types with additional functionality and convenience methods. | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/matthewgdv/subtypes |
| Metadata-Version: 2.1 | ||
| Name: pysubtypes | ||
| Version: 0.3.17 | ||
| Version: 0.3.18 | ||
| Summary: Provides subclasses for common python types with additional functionality and convenience methods. | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/matthewgdv/subtypes |
@@ -19,1 +19,3 @@ simplejson | ||
| urllib3 | ||
| xlsxwriter | ||
| msoffcrypto-tool |
+1
-1
| from setuptools import setup, find_packages | ||
| from os import path | ||
| __version__ = "0.3.17" | ||
| __version__ = "0.3.18" | ||
@@ -6,0 +6,0 @@ here = path.abspath(path.dirname(__file__)) |
| __all__ = [ | ||
| "cached_property", | ||
| "Enum", "ValueEnum", "AutoEnum", "ValueAutoEnum", | ||
| "Html", | ||
| "Enum", "ValueEnum", | ||
| "Html", "Xml", | ||
| "Http", | ||
@@ -15,12 +15,12 @@ "Singleton", | ||
| "Color", | ||
| "Translator" | ||
| "Translator", "TranslatableMeta", "DoNotTranslateMeta" | ||
| ] | ||
| from .lazy import cached_property | ||
| from .enums import Enum, ValueEnum, AutoEnum, ValueAutoEnum | ||
| from .markup import Html | ||
| from .enums import Enum, ValueEnum | ||
| from .markup import Html, Xml | ||
| from .http import Http | ||
| from .singleton import Singleton | ||
| from .namespace import NameSpace | ||
| from .translator import Translator | ||
| from .translator import Translator, TranslatableMeta, DoNotTranslateMeta | ||
| from .str import Str, BaseStr | ||
@@ -27,0 +27,0 @@ from .list import List, BaseList |
+30
-40
| from __future__ import annotations | ||
| from typing import List, Any, Type | ||
| from typing import Any, Callable | ||
| from collections.abc import Mapping | ||
| import json | ||
| from .lazy import cached_property | ||
| from .str import Str, Accessor | ||
| from .str import RegexAccessor as StrRegexAccessor, Settings | ||
| from .translator import Translator | ||
| from .translator import TranslatableMeta, DoNotTranslateMeta | ||
@@ -16,8 +14,7 @@ from maybe import Maybe | ||
| def is_special_private(name: str) -> bool: | ||
| return name.startswith("_") and name.endswith("_") | ||
| dict_fields = {attr for attr in dir(dict()) if not attr.startswith("_")} | ||
| def is_valid_for_attribute_actions(name: Any, dict_class: Type[Dict]) -> bool: | ||
| return isinstance(name, str) and name not in dict_class.settings.dict_fields and not is_special_private(name) and name.isidentifier() | ||
| def is_valid_for_attribute_actions(name: Any) -> bool: | ||
| return isinstance(name, str) and name not in dict_fields and name.isidentifier() | ||
@@ -31,8 +28,5 @@ | ||
| """An accessor class for all regex-related Dict methods""" | ||
| settings = StrRegexAccessor.Settings() | ||
| def __init__(self, parent: Dict = None) -> None: | ||
| default = type(self).settings | ||
| self.parent, self.settings = parent, StrRegexAccessor.Settings(dotall=default.dotall, ignorecase=default.ignorecase, multiline=default.multiline) | ||
| self.str = Str() | ||
| self.parent, self.settings = parent, StrRegexAccessor.Settings() | ||
@@ -83,6 +77,6 @@ def __call__(self, parent: Dict = None, dotall: bool = None, ignorecase: bool = None, multiline: bool = None) -> RegexAccessor: | ||
| def copy(self) -> BaseDict: | ||
| return type(self)(self.copy()) | ||
| return type(self)(self) | ||
| class Dict(BaseDict): | ||
| class Dict(BaseDict, metaclass=TranslatableMeta): | ||
| """ | ||
@@ -93,14 +87,10 @@ Subclass of the builtin 'dict' class with where inplace methods like dict.update() return self and therefore allow chaining. | ||
| class Settings(Settings): | ||
| def __init__(self) -> None: | ||
| self.re, self.dict_fields, self.translator, self.recursive = RegexAccessor.settings, {attr for attr in [*dir(dict()), "settings"] if not attr.startswith("_")}, Translator.default, True | ||
| class Accessors(Settings): | ||
| re = RegexAccessor | ||
| settings = Settings() | ||
| def __init__(self, *args: Any, **kwargs: Any) -> None: | ||
| super().__init__(*args, **kwargs) | ||
| if self.settings.recursive: | ||
| for key, val in self.items(): | ||
| self[key] = val | ||
| for key, val in self.items(): | ||
| self[key] = val | ||
@@ -115,6 +105,6 @@ def __getitem__(self, name: str) -> Any: | ||
| def __setitem__(self, name: str, val: Any) -> None: | ||
| clean_val = self.settings.translator.translate(val) if self.settings.recursive else val | ||
| clean_val = type(self).translator.translate(val) | ||
| super().__setitem__(name, clean_val) | ||
| super().__setitem__(name, clean_val) | ||
| if is_valid_for_attribute_actions(name, type(self)): | ||
| if is_valid_for_attribute_actions(name): | ||
| super().__setattr__(name, clean_val) | ||
@@ -125,13 +115,13 @@ | ||
| if is_valid_for_attribute_actions(name, type(self)): | ||
| if is_valid_for_attribute_actions(name): | ||
| super().__delattr__(name) | ||
| def __getattr__(self, name: str) -> Dict: | ||
| if is_special_private(name): | ||
| raise AttributeError(name) | ||
| else: | ||
| return self[name] | ||
| return self[name] | ||
| def __setattr__(self, name: str, val: Any) -> None: | ||
| if is_special_private(name): | ||
| if name in dict_fields: | ||
| raise AttributeError(f"Cannot assign to attribute '{type(self).__name__}.{name}'.") | ||
| if name.startswith("_") and name.endswith("_"): | ||
| super().__setattr__(name, val) | ||
@@ -142,6 +132,9 @@ else: | ||
| def __delattr__(self, name: str) -> None: | ||
| super().__delattr__(name) | ||
| if name in dict_fields: | ||
| raise AttributeError(f"Cannot delete attribute '{type(self).__name__}.{name}'.") | ||
| if not is_special_private(name): | ||
| super().__delitem__(name) | ||
| if name.startswith("_") and name.endswith("_"): | ||
| super().__delattr__(name) | ||
| else: | ||
| del self[name] | ||
@@ -157,5 +150,5 @@ def _factory_(self, name: str) -> Dict: | ||
| @cached_property | ||
| @property | ||
| def re(self) -> RegexAccessor: | ||
| return RegexAccessor(parent=self) | ||
| return self.Accessors.re(parent=self) | ||
@@ -173,7 +166,4 @@ def to_json(self, indent: int = 4, **kwargs: Any) -> str: | ||
| class DefaultDict(Dict): | ||
| class DefaultDict(Dict, metaclass=DoNotTranslateMeta): | ||
| def _factory_(self, name: str) -> DefaultDict: | ||
| return type(self)() | ||
| Translator.translations[dict] = Dict |
+3
-42
| from __future__ import annotations | ||
| from typing import Any, List | ||
| from typing import Any | ||
| import enum | ||
| import aenum | ||
| class EnumMeta(aenum.EnumMeta): | ||
| # Implementation of EnumMeta.__new__() and EnumMeta.__prepare__() alongside subtypes.Enum inheriting from enum.Enum unnecessarily | ||
| # when it already inherits from aenum.Enum which is a subclass of enum.Enum, is done so that the goddamn PyCharm type checker | ||
| # is satisfied that subtypes.Enum is, in fact, a freaking enumeration. Otherwise it just doesn't get it. | ||
| def __new__(mcs, name: str, bases: tuple, namespace: dict, init: Any = None, start: Any = None, settings: tuple = ()) -> EnumMeta: | ||
| if enum.Enum in bases: | ||
| bases = tuple(base for base in bases if not base == enum.Enum) | ||
| return super().__new__(mcs, name, bases, namespace, init, start, settings) | ||
| @classmethod | ||
| def __prepare__(mcs, name: str, bases: tuple, init: Any = None, start: Any = None, settings: tuple = ()) -> EnumMeta: | ||
| if enum.Enum in bases: | ||
| (bases := list(bases)).remove(enum.Enum) | ||
| bases = tuple(bases) | ||
| return super().__prepare__(name, bases, init, start, settings) | ||
| class EnumMeta(enum.EnumMeta): | ||
| def __repr__(cls) -> str: | ||
@@ -51,6 +31,2 @@ return f"{cls.__name__}[{', '.join([f'{member.name}={repr(member.value)}' for member in cls])}]" | ||
| def extend(cls, name: str, value: Any) -> None: | ||
| """Extend this Enum with an additional member created from the given name and value.""" | ||
| aenum.extend_enum(cls, name, value) | ||
| def is_enum(cls, candidate: Any) -> bool: | ||
@@ -74,3 +50,3 @@ """Returns True if the candidate is a subclass of Enum, otherwise returns False.""" | ||
| class BaseEnum(aenum.Enum): | ||
| class BaseEnum(enum.Enum): | ||
| def __repr__(self) -> str: | ||
@@ -104,16 +80,1 @@ return f"{type(self).__name__}(name={self.name}, value={repr(self.value)})" | ||
| """A subclass of subtypes.Enum. Attribute access on descendants of this class returns the value corresponding to that name, rather than returning the member.""" | ||
| class BaseAutoEnum(aenum.Enum): | ||
| _settings_ = aenum.AutoValue | ||
| def _generate_next_value_(name: str, start: str, count: str, last_values: list[str]) -> str: | ||
| return name.lower() | ||
| class AutoEnum(Enum, BaseAutoEnum): | ||
| """A subclass of subtypes.Enum. Automatically uses _generate_next_value_ when values are missing""" | ||
| class ValueAutoEnum(BaseEnum, BaseAutoEnum, metaclass=ValueEnumMeta): | ||
| """A subclass of subtypes.ValueEnum. Missing values are automatically supplied by _generate_next_value_.""" |
| from __future__ import annotations | ||
| import contextlib | ||
| import itertools | ||
@@ -8,3 +7,3 @@ import io | ||
| import os | ||
| from typing import Any, Collection, List, Dict, Union, Type, Iterable, TypeVar, Callable, Iterator, cast, TYPE_CHECKING | ||
| from typing import Any, Collection, Union, Type, Iterable, TypeVar, Callable, cast, TYPE_CHECKING | ||
| import pathlib | ||
@@ -15,5 +14,4 @@ | ||
| from pandas.io.sql import SQLTable, pandasSQL_builder | ||
| from pandas.io.excel._xlsxwriter import _XlsxWriter | ||
| from pandas.io.excel._xlsxwriter import XlsxWriter | ||
| from pandas.core.indexes.base import Index | ||
| import numpy as np | ||
| from maybe import Maybe | ||
@@ -66,2 +64,3 @@ | ||
| # noinspection PyFinal | ||
| class Frame(pd.DataFrame): | ||
@@ -177,3 +176,4 @@ columns: Union[Index, Iterable] | ||
| def convert_dtypes(self) -> Frame: | ||
| def convert_dtypes(self, infer_objects: bool = True, convert_string: bool = True, convert_integer: bool = True, convert_boolean: bool = True, convert_floating: bool = True,) -> Frame: | ||
| print(type(super().convert_dtypes())) | ||
| return type(self)(super().convert_dtypes()) | ||
@@ -385,3 +385,3 @@ | ||
| class ExcelWriter(_XlsxWriter): | ||
| class ExcelWriter(XlsxWriter): | ||
| def __init__(self, filepath: PathLike) -> None: | ||
@@ -388,0 +388,0 @@ super().__init__(os.fspath(filepath), engine="xlsxwriter", mode="w") |
+3
-3
@@ -10,3 +10,3 @@ from __future__ import annotations | ||
| from requests.exceptions import HTTPError | ||
| from requests.compat import quote, quote_plus | ||
| from urllib.parse import quote, quote_plus | ||
| from requests.adapters import HTTPAdapter | ||
@@ -17,3 +17,3 @@ | ||
| from .enums import Enum | ||
| from .translator import Translator | ||
| from .translator import TranslatableMeta | ||
@@ -30,3 +30,3 @@ | ||
| try: | ||
| return Translator.default.translate(super().json()) | ||
| return TranslatableMeta.translator.translate(super().json()) | ||
| except (json.JSONDecodeError, simplejson.JSONDecodeError): | ||
@@ -33,0 +33,0 @@ return None |
+9
-16
@@ -5,3 +5,3 @@ from __future__ import annotations | ||
| import json | ||
| from typing import Any, Iterable, Iterator, List, Callable, Union | ||
| from typing import Any, Iterable, Iterator, Callable, Union | ||
@@ -13,3 +13,3 @@ from .lazy import cached_property | ||
| from .str import Accessor, Settings | ||
| from .translator import Translator | ||
| from .translator import TranslatableMeta | ||
@@ -111,2 +111,3 @@ | ||
| # noinspection PyArgumentList | ||
| class BaseList(list): | ||
@@ -175,7 +176,7 @@ """ | ||
| def copy(self): | ||
| def copy(self) -> BaseList: | ||
| return type(self)(self) | ||
| class List(BaseList): | ||
| class List(BaseList, metaclass=TranslatableMeta): | ||
| """ | ||
@@ -189,16 +190,11 @@ Subclass of the builtin 'list' class with additional useful methods. All the 'list' class inplace methods return self and therefore allow chaining when called from this class. | ||
| class Settings(Settings): | ||
| translator, recursive = Translator.default, True | ||
| def __init__(self, iterable: Iterable = None) -> None: | ||
| super().__init__(iterable) if iterable is not None else super().__init__() | ||
| self.settings = self.Settings() | ||
| if self.settings.recursive: | ||
| for index, val in enumerate(self): | ||
| self[index] = self.settings.translator.translate(val) | ||
| for index, val in enumerate(self): | ||
| self[index] = type(self).translator.translate(val) | ||
| @cached_property | ||
| def slice(self) -> SliceAccessor: | ||
| return SliceAccessor(parent=self) | ||
| return self.Accessors.slice(parent=self) | ||
@@ -254,3 +250,3 @@ @cached_property | ||
| def flatten(self) -> List: | ||
| """Recursively traverses any Sequence objects within this List and unpacks them in order into a new flat List.""" | ||
| """Recursively traverses any non-textual Sequence objects within this List and unpacks them in order into a new flat List.""" | ||
| return self._flatten_more(iterable=self, output=type(self)()) | ||
@@ -274,4 +270,1 @@ | ||
| raise TypeError(f"The following json string resolves to type '{type(item).__name__}', not type '{list.__name__}':\n\n{json_string}") | ||
| Translator.translations[list] = List |
+69
-24
@@ -8,2 +8,3 @@ from __future__ import annotations | ||
| import html_text | ||
| from xml.dom.minidom import parseString as parse_xml | ||
@@ -13,29 +14,26 @@ from .str import Str | ||
| class Html(BeautifulSoup): | ||
| """A subclass of bs4.BeautifulSoup which uses 'html.parser' as its default parser.""" | ||
| class Markup(BeautifulSoup): | ||
| """A base class for BeautifulSoup markup classes to inherit from.""" | ||
| _stack = [] | ||
| parser = None | ||
| def __init__(self, html: str = None, features: str = "html.parser", **kwargs: Any) -> None: | ||
| element_classes = kwargs.get("element_classes", {}) | ||
| element_classes[Tag] = Html.Tag | ||
| kwargs["element_classes"] = element_classes | ||
| def __init__(self, html: str = None, features: str = None, **kwargs: Any) -> None: | ||
| kwargs["element_classes"] = {**{Tag: self.Tag}, **kwargs.get("element_classes", {})} | ||
| super().__init__(markup=html or "", features=features or self.parser, **kwargs) | ||
| self.tag = TagAccessor(self) | ||
| self._stack = [] | ||
| super().__init__(markup=html or "", features=features, **kwargs) | ||
| def __repr__(self, encoding="unicode-escape") -> str: | ||
| return str(self) | ||
| def __str__(self) -> str: | ||
| return Str(prettify_html(self.prettify())).re(multiline=True).sub(r"^( +)", lambda m: m.group(1)*4) | ||
| def __enter__(self) -> Html: | ||
| Html._stack.append(self) | ||
| def __enter__(self) -> Markup: | ||
| self._stack.append(self) | ||
| return self | ||
| def __exit__(self, ex_type: Any, ex_value: Any, ex_traceback: Any) -> None: | ||
| Html._stack.pop() | ||
| self._stack.pop() | ||
| def tag(self, name: str, content: str = None, /, attrs: dict = None, **kwattrs: Any) -> Html.Tag: | ||
| def _tag(self, name: str, content: str = None, /, attrs: dict = None, **kwattrs: Any) -> Markup.Tag: | ||
| tag = self.new_tag(name=name, attrs=attrs or {}, **kwattrs) | ||
| tag._root = self | ||
@@ -45,4 +43,4 @@ if content: | ||
| if Html._stack: | ||
| Html._stack[-1].append(tag) | ||
| if self._stack: | ||
| self._stack[-1].append(tag) | ||
| else: | ||
@@ -53,2 +51,23 @@ self.append(tag) | ||
| class Tag(Tag): | ||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
| self._root = None | ||
| def __enter__(self) -> Markup.Tag: | ||
| self._root._stack.append(self) | ||
| return self | ||
| def __exit__(self, ex_type: Any, ex_value: Any, ex_traceback: Any) -> None: | ||
| self._root._stack.pop() | ||
| class Html(Markup): | ||
| """A subclass of bs4.BeautifulSoup which uses 'lxml' as its default parser.""" | ||
| parser = "lxml" | ||
| def __str__(self) -> str: | ||
| return Str(prettify_html(self.prettify())).re(multiline=True).sub(r"^( +)", lambda m: m.group(1)*4) | ||
| @property | ||
@@ -58,8 +77,34 @@ def text(self) -> str: | ||
| class Tag(Tag): | ||
| def __enter__(self) -> Tag: | ||
| Html._stack.append(self) | ||
| return self | ||
| def __exit__(self, ex_type: Any, ex_value: Any, ex_traceback: Any) -> None: | ||
| Html._stack.pop() | ||
| class Xml(Markup): | ||
| """A subclass of bs4.BeautifulSoup which uses 'xml' as its default parser.""" | ||
| parser = "xml" | ||
| def __str__(self) -> str: | ||
| return Str(parse_xml(super().__str__()).toprettyxml()).re(multiline=True).sub(r"^(\t+)", lambda m: m.group(1).replace("\t", " ")) | ||
| class TagAccessor: | ||
| def __init__(self, parent: Markup) -> None: | ||
| self._parent = parent | ||
| def __call__(self, name: str, content: str = None, /, attrs: dict = None, **kwattrs: Any) -> Markup.Tag: | ||
| return self._parent._tag(name, content, attrs=attrs, **kwattrs) | ||
| def __getattr__(self, name) -> TagProxy: | ||
| return TagProxy(name, parent=self._parent) | ||
| class TagProxy: | ||
| def __init__(self, name: str, parent: Markup) -> None: | ||
| self._name, self._parent = name, parent | ||
| def __call__(self, content: str = None, /, attrs: dict = None, **kwattrs: Any) -> Markup.Tag: | ||
| return self._parent._tag(self._name, content, attrs=attrs, **kwattrs) | ||
| def __enter__(self) -> Markup.Tag: | ||
| return self._parent._tag(self._name).__enter__() | ||
| def __exit__(self, ex_type: Any, ex_value: Any, ex_traceback: Any) -> None: | ||
| pass |
+14
-19
@@ -5,4 +5,6 @@ from __future__ import annotations | ||
| import itertools | ||
| from functools import reduce | ||
| from operator import ior | ||
| import re | ||
| from typing import Any, Callable, Iterator, Iterable, List, Tuple, Dict, Mapping, Match, Union | ||
| from typing import Any, Callable, Iterator, Iterable, Tuple, Mapping, Match, Union | ||
| import warnings | ||
@@ -19,3 +21,3 @@ | ||
| from .enums import Enum | ||
| from .translator import Translator | ||
| from .translator import TranslatableMeta | ||
@@ -64,8 +66,5 @@ with warnings.catch_warnings(): | ||
| def to_flag(self) -> re.RegexFlag: | ||
| ret = re.RegexFlag(0) | ||
| for attr, flag in (self.dotall, re.DOTALL), (self.ignorecase, re.IGNORECASE), (self.multiline, re.MULTILINE): | ||
| if attr: | ||
| ret |= flag | ||
| return ret | ||
| def to_flag(self) -> int: | ||
| flags = [flag for attr, flag in [(self.dotall, re.DOTALL), (self.ignorecase, re.IGNORECASE), (self.multiline, re.MULTILINE)]] | ||
| return reduce(ior, flags) | ||
@@ -155,3 +154,2 @@ def __init__(self, parent: Str = None) -> None: | ||
| self.parent = Maybe(parent).else_(self.parent) | ||
| self.settings.acronyms = Maybe(acronyms).else_(self.settings.acronyms) | ||
| return self | ||
@@ -401,3 +399,3 @@ | ||
| class Str(BaseStr): | ||
| class Str(BaseStr, metaclass=TranslatableMeta): | ||
| """A subclass of the builin 'str' class which supports inplace mutation using item access. Has additional methods and accessor objects with additional methods for casing, regex, fuzzy-matching, trimming, and slicing.""" | ||
@@ -411,23 +409,23 @@ | ||
| class Accessors(Settings): | ||
| re, slice, fuzzy = RegexAccessor, SliceAccessor, FuzzyAccessor | ||
| re, case, slice, trim, fuzzy = RegexAccessor, CasingAccessor, SliceAccessor, TrimAccessor, FuzzyAccessor | ||
| @cached_property | ||
| def re(self) -> RegexAccessor: | ||
| return RegexAccessor(parent=self) | ||
| return self.Accessors.re(parent=self) | ||
| @cached_property | ||
| def case(self) -> CasingAccessor: | ||
| return CasingAccessor(parent=self) | ||
| return self.Accessors.case(parent=self) | ||
| @cached_property | ||
| def slice(self) -> SliceAccessor: | ||
| return SliceAccessor(parent=self) | ||
| return self.Accessors.slice(parent=self) | ||
| @cached_property | ||
| def trim(self) -> TrimAccessor: | ||
| return TrimAccessor(parent=self) | ||
| return self.Accessors.trim(parent=self) | ||
| @cached_property | ||
| def fuzzy(self) -> FuzzyAccessor: | ||
| return FuzzyAccessor(parent=self) | ||
| return self.Accessors.fuzzy(parent=self) | ||
@@ -455,4 +453,1 @@ def to_clipboard(self) -> None: | ||
| return cls(clipboard.paste()) | ||
| Translator.translations[str] = Str |
| from __future__ import annotations | ||
| from typing import Any, Dict, Type | ||
| from typing import Any, Type | ||
| from json import loads | ||
@@ -8,14 +8,10 @@ | ||
| class Translator: | ||
| translations: dict[Type, Type] = {} | ||
| default: Translator = None | ||
| def __init__(self, translations: dict = None) -> None: | ||
| self.translations = translations if translations is not None else self.translations.copy() | ||
| self.translations = translations or {} | ||
| def __call__(self, item: Any, recursive: bool = False) -> Any: | ||
| return (self.translate_recursively if recursive else self.translate)(item) | ||
| return self.translate_recursively(item) if recursive else self.translate(item) | ||
| def translate(self, item: Any) -> Any: | ||
| constructor = self.translations.get(type(item)) | ||
| return item if constructor is None else constructor(item) | ||
| return item if (constructor := self.translations.get(type(item))) is None else constructor(item) | ||
@@ -38,2 +34,11 @@ def translate_recursively(self, item: Any) -> Any: | ||
| Translator.default = Translator(translations=Translator.translations) | ||
| class TranslatableMeta(type): | ||
| translator = Translator() | ||
| def __init__(cls, name: str, bases: tuple, namespace: dict) -> None: | ||
| cls.translator.translations.update({base: cls for base in cls.mro()[1:-1]}) | ||
| class DoNotTranslateMeta(TranslatableMeta): | ||
| def __init__(cls, name: str, bases: tuple, namespace: dict) -> None: | ||
| pass |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
119876
-0.39%1723
-0.4%