PKsw?T4Ooenum_tools/demo.py# noqa: D100 # stdlib from enum import IntEnum, IntFlag from typing import List # this package import enum_tools.documentation __all__ = ["People", "NoMethods", "NoMemberDoc", "StatusFlags"] enum_tools.documentation.INTERACTIVE = True @enum_tools.documentation.document_enum class People(IntEnum): """ An enumeration of people. """ Bob = bob = 1 # noqa # doc: A person called Bob # doc: another doc # isort: ignore Alice = 2 # doc: A person called Alice Carol = 3 """ A person called Carol. This is a multiline docstring. """ @classmethod def iter_values(cls): """ Iterate over the values of the Enum. """ return iter(cls) # pragma: no cover #: A person called Dennis Dennis = 4 @classmethod def as_list(cls) -> List: """ Return the Enum's members as a list. """ return list(cls) # pragma: no cover @enum_tools.documentation.document_enum class NoMethods(IntEnum): """ An enumeration of people without any methods. """ Bob = bob = 1 # noqa # doc: A person called Bob # doc: another doc # isort: ignore Alice = 2 # doc: A person called Alice Carol = 3 # doc: A person called Carol @enum_tools.documentation.document_enum class NoMemberDoc(IntEnum): """ An enumeration of people without any member docstrings. """ Bob = bob = 1 Alice = 2 Carol = 3 @enum_tools.documentation.document_enum class StatusFlags(IntFlag): """ An enumeration of status codes. """ Running = 1 # doc: The system is running. Stopped = 2 # doc: The system has stopped. Error = 4 # doc: An error has occurred. def has_errored(self) -> bool: # pragma: no cover """ Returns whether the operation has errored. """ return (self & 4) == self.Error PKsw?Trenum_tools/custom_enums.py#!/usr/bin/env python3 # # custom_enums.py """ Custom subclasses of :class:`enum.Enum` and :class:`enum.Flag`. """ # # Copyright (c) 2020-2021 Dominic Davis-Foster # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # Parts based on https://docs.python.org/3/library/enum.html # and https://github.com/python/cpython/pull/22221 # and https://github.com/python/cpython/pull/22337 # PSF License 2.0 # # stdlib import sys from enum import Enum, Flag, IntFlag from typing import Any, Iterator __all__ = [ "MemberDirEnum", "IntEnum", "StrEnum", "AutoNumberEnum", "OrderedEnum", "DuplicateFreeEnum", "IterableFlag", "IterableIntFlag", ] if sys.version_info >= (3, 11): # pragma: no cover # stdlib from enum import _power_of_two def _decompose(flag, value): """ Extract all members from the value. """ # From CPython. Removed in https://github.com/python/cpython/pull/24215 # _decompose is only called if the value is not named not_covered = value # issue29167: wrap accesses to _value2member_map_ in a list to avoid race # conditions between iterating over it and having more pseudo- # members added to it flags_to_check = [] if value < 0: # only check for named flags for v, m in list(flag._value2member_map_.items()): if m.name is not None: flags_to_check.append((m, v)) else: # check for named flags and powers-of-two flags for v, m in list(flag._value2member_map_.items()): if m.name is not None or _power_of_two(v): flags_to_check.append((m, v)) members = [] for member, member_value in flags_to_check: if member_value and member_value & value == member_value: members.append(member) not_covered &= ~member_value if not members and value in flag._value2member_map_: members.append(flag._value2member_map_[value]) members.sort(key=lambda m: m._value_, reverse=True) if len(members) > 1 and members[0].value == value: # we have the breakdown, don't need the value member itself members.pop(0) return members, not_covered else: # pragma: no cover (py310+) # stdlib from enum import _decompose # type: ignore class MemberDirEnum(Enum): """ :class:`~enum.Enum` which includes attributes as well as methods. This will be part of the :mod:`enum` module starting with Python 3.10. .. seealso:: Pull request :pull:`19219 ` by Angelin BOOZ, which added this to CPython. .. versionadded:: 0.6.0 """ def __dir__(self): return super().__dir__() + [m for m in self.__dict__ if m[0] != '_'] class IntEnum(int, Enum): """ :class:`~enum.Enum` where members are also (and must be) ints. """ # def __int__(self): # return self.value # def __eq__(self, other): # if int(self) == other: # return True # else: # return super().__eq__(other) class StrEnum(str, Enum): """ :class:`~enum.Enum` where members are also (and must be) strings. """ def __str__(self) -> str: return self.value def __new__(cls, *values): # noqa: D102 if len(values) > 3: raise TypeError(f'too many arguments for str(): {values!r}') if len(values) == 1: # it must be a string if not isinstance(values[0], str): raise TypeError(f'{values[0]!r} is not a string') if len(values) > 1: # check that encoding argument is a string if not isinstance(values[1], str): raise TypeError(f'encoding must be a string, not {values[1]!r}') if len(values) > 2: # check that errors argument is a string if not isinstance(values[2], str): raise TypeError(f'errors must be a string, not {values[2]!r}') value = str(*values) member = str.__new__(cls, value) member._value_ = value return member # def __repr__(self): # return self.value # def __eq__(self, other): # if str(self) == other: # return True # else: # return super().__eq__(other) class AutoNumberEnum(Enum): """ :class:`~enum.Enum` that automatically assigns increasing values to members. """ def __new__(cls, *args, **kwds) -> Any: # noqa: D102 value = len(cls.__members__) + 1 obj = object.__new__(cls) obj._value_ = value return obj class OrderedEnum(Enum): """ :class:`~enum.Enum` that adds ordering based on the values of its members. """ def __ge__(self, other) -> bool: if self.__class__ is other.__class__: return self._value_ >= other._value_ return NotImplemented def __gt__(self, other) -> bool: if self.__class__ is other.__class__: return self._value_ > other._value_ return NotImplemented def __le__(self, other) -> bool: if self.__class__ is other.__class__: return self._value_ <= other._value_ return NotImplemented def __lt__(self, other) -> bool: if self.__class__ is other.__class__: return self._value_ < other._value_ return NotImplemented class DuplicateFreeEnum(Enum): """ :class:`~enum.Enum` that disallows duplicated member names. """ def __init__(self, *args) -> None: cls = self.__class__ if any(self.value == e.value for e in cls): a = self.name e = cls(self.value).name raise ValueError(f"aliases are not allowed in DuplicateFreeEnum: {a!r} --> {e!r}") class IterableFlag(Flag): """ :class:`~enum.Flag` with support for iterating over members and member combinations. This functionality was added to Python 3.10's :mod:`enum` module in :pull:`22221 `. .. versionadded:: 0.5.0 """ def __iter__(self) -> Iterator[Flag]: """ Returns members in definition order. :rtype: .. latex:clearpage:: """ members, extra_flags = _decompose(self.__class__, self.value) return (m for m in members if m._value_ != 0) class IterableIntFlag(IntFlag): """ :class:`~enum.IntFlag` with support for iterating over members and member combinations. This functionality was added to Python 3.10's :mod:`enum` module in :pull:`22221 `. .. versionadded:: 0.5.0 """ def __iter__(self) -> Iterator[IntFlag]: """ Returns members in definition order. """ members, extra_flags = _decompose(self.__class__, self.value) return (m for m in members if m._value_ != 0) PKsw?TP_x { { enum_tools/utils.py#!/usr/bin/env python3 # # utils.py """ General utility functions. """ # # Copyright (c) 2020-2021 Dominic Davis-Foster # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # stdlib import inspect from enum import Enum, EnumMeta, Flag from typing import Tuple, Type # 3rd party from typing_extensions import Protocol, runtime_checkable __all__ = ["HasMRO", "is_enum", "is_enum_member", "is_flag", "get_base_object"] @runtime_checkable class HasMRO(Protocol): """ :class:`typing.Protocol` for classes that have a method resolution order magic method (``__mro__``). """ @property def __mro__(self) -> Tuple[Type]: ... def is_enum(obj: Type) -> bool: """ Returns :py:obj:`True` if ``obj`` is an :class:`enum.Enum`. :param obj: """ # The enum itself is subclass of EnumMeta; enum members subclass Enum return isinstance(obj, EnumMeta) def is_enum_member(obj: Type) -> bool: """ Returns :py:obj:`True` if ``obj`` is an :class:`enum.Enum` member. :param obj: """ # The enum itself is subclass of EnumMeta; enum members subclass Enum return isinstance(obj, Enum) def is_flag(obj: Type) -> bool: """ Returns :py:obj:`True` if ``obj`` is an :class:`enum.Flag`. :param obj: """ # The enum itself is subclass of EnumMeta; enum members subclass Enum if is_enum(obj) and isinstance(obj, HasMRO): return Flag in inspect.getmro(obj) else: return False def get_base_object(enum: Type[HasMRO]) -> Type: """ Returns the object type of the enum's members. If the members are of indeterminate type then the :class:`object` class is returned. :param enum: :rtype: :raises TypeError: If ``enum`` is not an Enum. """ try: mro = inspect.getmro(enum) except AttributeError: raise TypeError("not an Enum") if Flag in mro: mro = mro[:mro.index(Flag)] elif Enum in mro: mro = mro[:mro.index(Enum)] else: raise TypeError("not an Enum") mro = mro[1:] for obj in mro: if not isinstance(obj, EnumMeta): return obj return object PKsw?TxؗV  enum_tools/__init__.py#!/usr/bin/env python3 # # __init__.py """ Tools to expand Python's enum module. """ # # Copyright (c) 2020 Dominic Davis-Foster # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # stdlib from enum import Enum, Flag, IntFlag # this package from enum_tools.custom_enums import AutoNumberEnum, DuplicateFreeEnum, IntEnum, OrderedEnum, StrEnum from enum_tools.documentation import DocumentedEnum, document_enum, document_member __author__: str = "Dominic Davis-Foster" __copyright__: str = "2020 Dominic Davis-Foster" __license__: str = "GNU Lesser General Public License v3 or later (LGPLv3+)" __version__: str = "0.9.0" __email__: str = "dominic@davis-foster.co.uk" __all__ = [ "Enum", "IntEnum", "StrEnum", "AutoNumberEnum", "OrderedEnum", "DuplicateFreeEnum", "Flag", "IntFlag", "DocumentedEnum", "document_enum", "document_member", ] PKsw?T] ȔKKenum_tools/autoenum.py#!/usr/bin/env python3 # # autoenum.py """ A Sphinx directive for documenting :class:`Enums ` in Python. """ # # Copyright (c) 2020-2022 Dominic Davis-Foster # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # Parts based on https://github.com/sphinx-doc/sphinx # | Copyright (c) 2007-2020 by the Sphinx team (see AUTHORS file). # | BSD Licensed # | All rights reserved. # | # | Redistribution and use in source and binary forms, with or without # | modification, are permitted provided that the following conditions are # | met: # | # | * Redistributions of source code must retain the above copyright # | notice, this list of conditions and the following disclaimer. # | # | * Redistributions in binary form must reproduce the above copyright # | notice, this list of conditions and the following disclaimer in the # | documentation and/or other materials provided with the distribution. # | # | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # stdlib from contextlib import suppress from enum import Enum from typing import Any, Dict, List, Optional, Tuple, get_type_hints # 3rd party import sphinx # nodep from docutils.nodes import Element # nodep from sphinx.application import Sphinx # nodep from sphinx.domains import ObjType # nodep from sphinx.domains.python import PyClasslike, PyXRefRole # nodep from sphinx.environment import BuildEnvironment # nodep from sphinx.errors import PycodeError # nodep from sphinx.ext.autodoc import ( # nodep ALL, INSTANCEATTR, SUPPRESS, AttributeDocumenter, ClassDocumenter, ClassLevelDocumenter, Documenter, logger ) from sphinx.locale import _ # nodep from sphinx.pycode import ModuleAnalyzer # nodep from sphinx.util.inspect import memory_address_re, safe_getattr # nodep from sphinx.util.typing import stringify as stringify_typehint # nodep from sphinx_toolbox.more_autodoc.typehints import format_annotation # nodep from sphinx_toolbox.utils import add_fallback_css_class # nodep from sphinx_toolbox.utils import unknown_module_warning # nodep # this package from enum_tools import __version__, documentation from enum_tools.utils import get_base_object, is_enum, is_flag __all__ = ["EnumDocumenter", "EnumMemberDocumenter", "setup", "FlagDocumenter", "PyEnumXRefRole"] documentation.INTERACTIVE = True _filename_set_attribute = "filename_set" if sphinx.version_info < (4, 0) else "record_dependencies" def _begin_generate( documenter: Documenter, real_modname: Optional[str] = None, check_module: bool = False, ) -> Optional[str]: """ Boilerplate for the top of ``generate`` in :class:`sphinx.ext.autodoc.Documenter` subclasses. :param documenter: :param real_modname: :param check_module: :return: The ``sourcename``, or :py:obj:`None` if certain conditions are met, to indicate that the Documenter class should exit early. """ # Do not pass real_modname and use the name from the __module__ # attribute of the class. # If a class gets imported into the module real_modname # the analyzer won't find the source of the class, if # it looks in real_modname. if not documenter.parse_name(): # need a module to import unknown_module_warning(documenter) return None # now, import the module and get object to document if not documenter.import_object(): return None # If there is no real module defined, figure out which to use. # The real module is used in the module analyzer to look up the module # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. guess_modname = documenter.get_real_modname() documenter.real_modname = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: documenter.analyzer = ModuleAnalyzer.for_module(documenter.real_modname) # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) documenter.analyzer.find_attr_docs() except PycodeError as err: # pragma: no cover logger.debug("[autodoc] module analyzer failed: %s", err) # no source file -- e.g. for builtin and C modules documenter.analyzer = None # type: ignore # at least add the module.__file__ as a dependency if hasattr(documenter.module, "__file__") and documenter.module.__file__: filename_set = getattr(documenter.directive, _filename_set_attribute) filename_set.add(documenter.module.__file__) else: filename_set = getattr(documenter.directive, _filename_set_attribute) filename_set.add(documenter.analyzer.srcname) if documenter.real_modname != guess_modname: # Add module to dependency list if target object is defined in other module. with suppress(PycodeError): analyzer = ModuleAnalyzer.for_module(guess_modname) filename_set = getattr(documenter.directive, _filename_set_attribute) filename_set.add(analyzer.srcname) # check __module__ of object (for members not given explicitly) if check_module: if not documenter.check_module(): return None sourcename = documenter.get_sourcename() # make sure that the result starts with an empty line. This is # necessary for some situations where another directive preprocesses # reST and no starting newline is present documenter.add_line('', sourcename) return sourcename class EnumDocumenter(ClassDocumenter): r""" Sphinx autodoc :class:`~sphinx.ext.autodoc.Documenter` for documenting :class:`~enum.Enum`\s. """ objtype = "enum" directivetype = "enum" priority = 20 class_xref = ":class:`~enum.Enum`" @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any) -> bool: """ Called to see if a member can be documented by this documenter. :param member: :param membername: :param isattr: :param parent: """ return is_enum(member) and not is_flag(member) def document_members(self, all_members: bool = False) -> None: """ Generate reST for member documentation. :param all_members: If :py:obj:`True`, document all members, otherwise document those given by else those given by ``self.options.members``. .. latex:clearpage:: """ if self.doc_as_attr: return # print(self.directive.result) # input("> ") # set current namespace for finding members self.env.temp_data["autodoc:module"] = self.modname if self.objpath: self.env.temp_data["autodoc:class"] = self.objpath[0] want_all = all_members or self.options.inherited_members or self.options.members is ALL # find out which members are documentable members_check_module, members = self.get_object_members(want_all) non_enum_members = [] for member in members: if member[0] not in self.object.__members__.keys(): non_enum_members.append(member) user_option_undoc_members = self.options.undoc_members # Document enums first self.options.undoc_members = True # type: ignore enum_members = [(var.name, var) for var in self.object] self._do_document_members( enum_members, want_all, members_check_module, description="Valid values are as follows:", ) # Document everything else self.options.undoc_members = user_option_undoc_members # type: ignore methods_text = ( f"The {self.class_xref} and its members " f"{'also ' if enum_members else ''}have the following methods:" ) self._do_document_members( non_enum_members, want_all, members_check_module, description=methods_text, ) def _do_document_members(self, members, want_all, members_check_module, description): # remove members given by exclude-members if self.options.exclude_members: members = [ (membername, member) for (membername, member) in members # noqa if (self.options.exclude_members is ALL or membername not in self.options.exclude_members) ] # document non-skipped members memberdocumenters: List[Tuple[Documenter, bool]] = [] description_added = False for (mname, member, isattr) in self.filter_members(members, want_all): if not description_added: self.add_line(description, self.sourcename) self.add_line('', self.sourcename) description_added = True # give explicitly separated module name, so that members # of inner classes can be documented full_mname = self.modname + "::" + '.'.join(self.objpath + [mname]) documenter: Documenter if isinstance(member, Enum) and member in self.object: documenter = EnumMemberDocumenter(self.directive, full_mname, self.indent) else: classes = [ cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self) ] if not classes: # don't know how to document this member continue # prefer the documenter with the highest priority classes.sort(key=lambda cls: cls.priority) documenter = classes[-1](self.directive, full_mname, self.indent) memberdocumenters.append((documenter, isattr)) member_order = self.options.member_order or self.env.config.autodoc_member_order if member_order == "groupwise": # sort by group; relies on stable sort to keep items in the # same group sorted alphabetically memberdocumenters.sort(key=lambda e: e[0].member_order) elif member_order == "bysource" and self.analyzer: # sort by source order, by virtue of the module analyzer tagorder = self.analyzer.tagorder def keyfunc(entry: Tuple[Documenter, bool]) -> int: fullname = entry[0].name.split("::")[1] return tagorder.get(fullname, len(tagorder)) memberdocumenters.sort(key=keyfunc) for documenter, isattr in memberdocumenters: documenter.generate( all_members=True, real_modname=self.real_modname, check_module=members_check_module and not isattr, ) # reset current objects self.env.temp_data["autodoc:module"] = None self.env.temp_data["autodoc:class"] = None real_modname: str def generate( self, more_content: Optional[Any] = None, real_modname: Optional[str] = None, check_module: bool = False, all_members: bool = False, ) -> None: """ Generate reST for the object given by *self.name*, and possibly for its members. :param more_content: Additional content to include in the reST output. :param real_modname: Module name to use to find attribute documentation. :param check_module: If :py:obj:`True`, only generate if the object is defined in the module name it is imported from. :param all_members: If :py:obj:`True`, document all members. """ ret = _begin_generate(self, real_modname, check_module) if ret is None: return sourcename = ret # Set sourcename as instance variable to avoid passing it around; it will get deleted later self.sourcename = sourcename # generate the directive header and options, if applicable self.add_directive_header("(value)") self.add_line('', sourcename) self.indent += self.content_indent # add all content (from docstrings, attribute docs etc.) self.add_content(more_content) member_type = get_base_object(self.object) if member_type is not object: # Show the type of the members self.add_line(f":Member Type: {format_annotation(member_type)}", self.sourcename) self.add_line('', self.sourcename) # document members, if possible self.document_members(all_members) del self.sourcename class FlagDocumenter(EnumDocumenter): r""" Sphinx autodoc :class:`~sphinx.ext.autodoc.Documenter` for documenting :class:`~enum.Flag`\s. .. autosummary-widths:: 55/100 """ objtype = "flag" directivetype = "flag" priority = 15 class_xref = ":class:`~enum.Flag`" @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any) -> bool: """ Called to see if a member can be documented by this documenter. :param member: :param membername: :param isattr: :param parent: """ return is_flag(member) class EnumMemberDocumenter(AttributeDocumenter): """ Sphinx autodoc :class:`~sphinx.ext.autodoc.Documenter` for documenting :class:`~enum.Enum` members. """ def import_object(self, raiseerror: bool = False) -> bool: """ Import the object given by ``self.modname`` and ``self.objpath`` and set it as ``self.object``. :param raiseerror: :returns: :py:obj:`True` if successful, :py:obj:`False` if an error occurred. .. latex:clearpage:: """ return Documenter.import_object(self, raiseerror=raiseerror) def generate( self, more_content: Optional[Any] = None, real_modname: Optional[str] = None, check_module: bool = False, all_members: bool = False ) -> None: """ Generate reST for the object given by ``self.name``, and possibly for its members. :param more_content: Additional content to include in the reST output. :param real_modname: Module name to use to find attribute documentation. :param check_module: If :py:obj:`True`, only generate if the object is defined in the module name it is imported from. :param all_members: If :py:obj:`True`, document all members. .. versionchanged:: 0.8.0 Multiline docstrings are now correctly represented in the generated output. """ ret = _begin_generate(self, real_modname, check_module) if ret is None: return sourcename = ret # generate the directive header and options, if applicable self.add_directive_header('') self.add_line('', sourcename) # e.g. the module directive doesn't have content self.indent += self.content_indent # Add the value's docstring if self.object.__doc__ and self.object.__doc__ != self.object.__class__.__doc__: # Lines of multiline docstrings need to be added one by one. for line in self.object.__doc__.splitlines(): self.add_line(line, sourcename) self.add_line('', sourcename) def add_directive_header(self, sig: str) -> None: """ Add the directive header for the Enum member. :param sig: """ ClassLevelDocumenter.add_directive_header(self, sig) sourcename = self.get_sourcename() if not self.options.annotation: # obtain type annotation for this attribute try: annotations = get_type_hints(self.parent) except NameError: # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) annotations = safe_getattr(self.parent, "__annotations__", {}) except (TypeError, KeyError, AttributeError): # KeyError = a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) # AttributeError is raised on 3.5.2 (fixed by 3.5.3) annotations = {} if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) self.add_line(" :type: " + objrepr, sourcename) else: key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) if self.analyzer and key in self.analyzer.annotations: self.add_line(" :type: " + self.analyzer.annotations[key], sourcename) elif self.options.annotation is SUPPRESS: pass else: self.add_line(" :annotation: %s" % self.options.annotation, sourcename) if not self.options.annotation: with suppress(Exception): if self.object is not INSTANCEATTR: # Workaround for https://github.com/sphinx-doc/sphinx/issues/9272 # which broke Enum displays in 4.1.0 objrepr = memory_address_re.sub('', repr(self.object)).replace('\n', ' ') self.add_line(f' :value: {objrepr}', self.get_sourcename()) class PyEnumXRefRole(PyXRefRole): """ XRefRole for Enum/Flag members. .. versionadded:: 0.4.0 .. autosummary-widths:: 40/100 """ def process_link( self, env: BuildEnvironment, refnode: Element, has_explicit_title: bool, title: str, target: str, ) -> Tuple[str, str]: """ Called after parsing title and target text, and creating the reference node (given in ``refnode``). This method can alter the reference node and must return a new (or the same) ``(title, target)`` tuple. :param env: :param refnode: :param has_explicit_title: :param title: :param target: :rtype: .. latex:clearpage:: """ refnode["py:module"] = env.ref_context.get("py:module") refnode["py:class"] = env.ref_context.get("py:class") if not has_explicit_title: title = title.lstrip('.') # only has a meaning for the target target = target.lstrip("~+") # only has a meaning for the title # if the first character is a tilde, don't display the module/class # parts of the contents if title[0:1] == '~': title = '.'.join(title[1:].split('.')[-2:]) elif title[0:1] == '+': title = title[1:] dot = title.rfind('.') if dot != -1: title = title[dot + 1:] # if the first character is a dot, search more specific namespaces first # else search builtins first if target[0:1] == '.': target = target[1:] refnode["refspecific"] = True return title, target def setup(app: Sphinx) -> Dict[str, Any]: """ Setup Sphinx Extension. :param app: """ app.registry.domains["py"].object_types["enum"] = ObjType(_("enum"), "enum", "class", "obj") app.add_directive_to_domain("py", "enum", PyClasslike) app.add_role_to_domain("py", "enum", PyXRefRole()) app.registry.domains["py"].object_types["flag"] = ObjType(_("flag"), "flag", "enum", "class", "obj") app.add_directive_to_domain("py", "flag", PyClasslike) app.add_role_to_domain("py", "flag", PyXRefRole()) app.add_role_to_domain("py", "enum:mem", PyEnumXRefRole()) app.add_role_to_domain("py", "enum:member", PyEnumXRefRole()) app.add_role_to_domain("py", "flag:mem", PyEnumXRefRole()) app.add_role_to_domain("py", "flag:member", PyEnumXRefRole()) app.add_autodocumenter(EnumDocumenter) app.add_autodocumenter(FlagDocumenter) app.connect("object-description-transform", add_fallback_css_class({"enum": "class", "flag": "class"})) return { "version": __version__, "parallel_read_safe": True, "parallel_write_safe": True, } PKsw?Tenum_tools/py.typedPKsw?T+$**enum_tools/documentation.py#!/usr/bin/env python3 # # documentation.py """ Decorators to add docstrings to enum members from comments. """ # # Copyright (c) 2020-2021 Dominic Davis-Foster # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # stdlib import ast import inspect import re import sys import tokenize import warnings from enum import Enum, EnumMeta from textwrap import dedent from typing import Iterable, List, Optional, Sequence, Tuple, TypeVar, Union # 3rd party import pygments.token # type: ignore from pygments.lexers.python import PythonLexer # type: ignore __all__ = [ "get_tokens", "document_enum", "document_member", "parse_tokens", "get_base_indent", "DocumentedEnum", "get_dedented_line", "MultipleDocstringsWarning", ] _lexer = PythonLexer() INTERACTIVE = bool(getattr(sys, "ps1", sys.flags.interactive)) EnumType = TypeVar("EnumType", bound=EnumMeta) def get_tokens(line: str) -> List[Tuple]: """ Returns a list ot tokens generated from the given Python code. :param line: Line of Python code to tokenise. """ return list(_lexer.get_tokens(line)) def _docstring_from_expr(expr: ast.Expr) -> Optional[str]: """ Check if the expression is a docstring. :param expr: :returns: The cleaned docstring text if it is a docstring, or :py:obj:`None` if it isn't. """ # might be docstring docstring_node = expr.value if isinstance(docstring_node, ast.Str): text = docstring_node.s elif isinstance(docstring_node, ast.Constant) and isinstance(docstring_node.value, str): text = docstring_node.value else: # not a docstring return None return inspect.cleandoc(text) def _docstring_from_eol_comment( source: str, node: Union[ast.Assign, ast.AnnAssign], ) -> Optional[str]: """ Search for an end-of-line docstring comment (starts with ``# doc:``). :param source: The source of the Enum class. :param node: The AST node for the Enum member. """ toks = _tokenize_line(source.split('\n')[node.lineno - 1]) comment_toks = [x for x in list(toks) if x.type == tokenize.COMMENT] if comment_toks: for match in re.finditer(r"(doc:\s*)([^#]*)(#|$)", comment_toks[0].string): if match.group(2): return match.group(2).rstrip() return None def _docstring_from_sphinx_comment( source: str, node: Union[ast.Assign, ast.AnnAssign], ) -> Optional[str]: """ Search for a Sphinx-style docstring comment (starts with ``#:``). :param source: The source of the Enum class. :param node: The AST node for the Enum member. """ for offset in range(node.lineno - 1, 0, -1): line = source.split('\n')[offset - 1] if line.strip(): # contains non-whitespace try: toks = _tokenize_line(line) except (tokenize.TokenError, SyntaxError): return None # print(list(toks)) comment_toks = [x for x in list(toks) if x.type == tokenize.COMMENT] if comment_toks: for match in re.finditer(r"(#:\s*)(.*)", comment_toks[0].string): if match.group(2): return match.group(2).rstrip() return None return None def _tokenize_line(line: str) -> List[tokenize.TokenInfo]: """ Tokenize a single line of Python source code. :param line: """ def yielder(): yield line return list(tokenize.generate_tokens(yielder().__next__)) class MultipleDocstringsWarning(UserWarning): """ Warning emitted when multiple docstrings are found for a single Enum member. .. versionadded:: 0.8.0 :param member: :param docstrings: The list of docstrings found for the member. """ #: The member with multiple docstrings. member: Enum #: The list of docstrings found for the member. docstrings: Iterable[str] def __init__(self, member: Enum, docstrings: Iterable[str] = ()): self.member = member self.docstrings = docstrings def __str__(self) -> str: member_full_name = '.'.join([ self.member.__class__.__module__, self.member.__class__.__name__, self.member.name, ]) return f"Found multiple docstrings for enum member <{member_full_name}>" def document_enum(an_enum: EnumType) -> EnumType: """ Document all members of an enum by parsing a docstring from the Python source.. The docstring can be added in several ways: #. A comment at the end the line, starting with ``doc:``: .. code-block:: python Running = 1 # doc: The system is running. #. A comment on the previous line, starting with ``#:``. This is the format used by Sphinx. .. code-block:: python #: The system is running. Running = 1 #. A string on the line *after* the attribute. This can be used for multiline docstrings. .. code-block:: python Running = 1 \"\"\" The system is running. Hello World \"\"\" If more than one docstring format is found for an enum member a :exc:`MultipleDocstringsWarning` is emitted. :param an_enum: An :class:`~enum.Enum` subclass :type an_enum: :class:`enum.Enum` :returns: The same object passed as ``an_enum``. This allows this function to be used as a decorator. :rtype: :class:`enum.Enum` .. versionchanged:: 0.8.0 Added support for other docstring formats and multiline docstrings. """ if not isinstance(an_enum, EnumMeta): raise TypeError(f"'an_enum' must be an 'Enum', not {type(an_enum)}!") if not INTERACTIVE: return an_enum func_source = dedent(inspect.getsource(an_enum)) func_source_tree = ast.parse(func_source) assert len(func_source_tree.body) == 1 module_body = func_source_tree.body[0] assert isinstance(module_body, ast.ClassDef) class_body = module_body.body for idx, node in enumerate(class_body): targets = [] if isinstance(node, ast.Assign): for t in node.targets: assert isinstance(t, ast.Name) targets.append(t.id) elif isinstance(node, ast.AnnAssign): assert isinstance(node.target, ast.Name) targets.append(node.target.id) else: continue assert isinstance(node, (ast.Assign, ast.AnnAssign)) # print(targets) if idx + 1 == len(class_body): next_node = None else: next_node = class_body[idx + 1] docstring_candidates = [] if isinstance(next_node, ast.Expr): # might be docstring docstring_candidates.append(_docstring_from_expr(next_node)) # maybe no luck with """ docstring? look for EOL comment. docstring_candidates.append(_docstring_from_eol_comment(func_source, node)) # check non-whitespace lines above for Sphinx-style comment. docstring_candidates.append(_docstring_from_sphinx_comment(func_source, node)) docstring_candidates_nn = list(filter(None, docstring_candidates)) if len(docstring_candidates_nn) > 1: # Multiple docstrings found, warn warnings.warn(MultipleDocstringsWarning(getattr(an_enum, targets[0]), docstring_candidates_nn)) if docstring_candidates_nn: docstring = docstring_candidates_nn[0] for target in targets: getattr(an_enum, target).__doc__ = docstring return an_enum def document_member(enum_member: Enum) -> None: """ Document a member of an enum by adding a comment to the end of the line that starts with ``doc:``. :param enum_member: A member of an :class:`~enum.Enum` subclass """ if not isinstance(enum_member, Enum): raise TypeError(f"'an_enum' must be an 'Enum', not {type(enum_member)}!") if not INTERACTIVE: return None func_source = dedent(inspect.getsource(enum_member.__class__)) in_docstring = False base_indent = None for line in func_source.split('\n'): indent, line = get_dedented_line(line) if line.startswith("class") or not line: continue all_tokens = get_tokens(line) base_indent = get_base_indent(base_indent, all_tokens, indent) # print(all_tokens) if enum_member.name not in line: continue if all_tokens[0][0] in pygments.token.Literal.String: if all_tokens[0][1] in {'"""', "'''"}: # TODO: handle the other quotes appearing in docstring in_docstring = not in_docstring if all_tokens[0][0] in pygments.token.Name and in_docstring: continue elif all_tokens[0][0] not in pygments.token.Name: continue else: if indent > base_indent: # type: ignore continue enum_vars, doc = parse_tokens(all_tokens) for var in enum_vars: # print(repr(var)) if not var.startswith('@'): if var == enum_member.name: enum_member.__doc__ = doc return None def parse_tokens(all_tokens: Iterable["pygments.Token"]) -> Tuple[List, Optional[str]]: """ Parse the tokens representing a line of code to identify Enum members and ``doc:`` comments. :param all_tokens: :return: A list of the Enum members' names, and the docstring for them. """ enum_vars = [] doc = None comment = '' for token in all_tokens: if token[0] in pygments.token.Name: enum_vars.append(token[1]) elif token[0] in pygments.token.Comment: comment = token[1] break for match in re.finditer(r"(doc:\s*)([^#]*)(#|$)", comment): if match.group(2): doc = match.group(2).rstrip() break return enum_vars, doc def get_base_indent( base_indent: Optional[int], all_tokens: Sequence[Sequence], indent: int, ) -> Optional[int]: """ Determine the base level of indentation (i.e. one level of indentation in from the ``c`` of ``class``). :param base_indent: The current base level of indentation :param all_tokens: :param indent: The current level of indentation :returns: The base level of indentation """ if not base_indent: if all_tokens[0][0] in pygments.token.Literal.String: if all_tokens[0][1] in {'"""', "'''"}: base_indent = indent elif all_tokens[0][0] in pygments.token.Keyword: base_indent = indent elif all_tokens[0][0] in pygments.token.Name: base_indent = indent return base_indent class DocumentedEnum(Enum): """ An enum where docstrings are automatically added to members from comments starting with ``doc:``. .. note:: This class does not (yet) support the other docstring formats :deco:`~.document_enum` does. """ def __init__(self, value): document_member(self) # super().__init__(value) def get_dedented_line(line: str) -> Tuple[int, str]: """ Returns the line without indentation, and the amount of indentation. :param line: A line of Python source code """ dedented_line = dedent(line) indent = len(line) - len(dedented_line) line = dedented_line.strip() return indent, line PK x?TTT enum_tools-0.9.0.dist-info/WHEELWheel-Version: 1.0 Generator: whey (0.0.17) Root-Is-Purelib: true Tag: py3-none-any PK x?Td,#enum_tools-0.9.0.dist-info/METADATAMetadata-Version: 2.1 Name: enum-tools Version: 0.9.0 Summary: Tools to expand Python's enum module. Author-email: Dominic Davis-Foster License: LGPL-3.0-or-later Keywords: documentation,enum,sphinx,sphinx-extension Home-page: https://github.com/domdfcoding/enum_tools Project-URL: Issue Tracker, https://github.com/domdfcoding/enum_tools/issues Project-URL: Source Code, https://github.com/domdfcoding/enum_tools Project-URL: Documentation, https://enum_tools.readthedocs.io/en/latest Platform: Windows Platform: macOS Platform: Linux Classifier: Development Status :: 4 - Beta Classifier: Framework :: Sphinx :: Extension Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Documentation Classifier: Topic :: Documentation :: Sphinx Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities Classifier: Typing :: Typed Requires-Python: >=3.6 Requires-Dist: pygments>=2.6.1 Requires-Dist: typing-extensions>=3.7.4.3 Provides-Extra: sphinx Requires-Dist: sphinx>=3.2.0; extra == 'sphinx' Requires-Dist: sphinx-toolbox>=2.16.0; extra == 'sphinx' Provides-Extra: all Requires-Dist: sphinx>=3.2.0; extra == 'all' Requires-Dist: sphinx-toolbox>=2.16.0; extra == 'all' Description-Content-Type: text/x-rst ============ Enum Tools ============ .. start short_desc **Tools to expand Python's enum module.** .. end short_desc .. start shields .. list-table:: :stub-columns: 1 :widths: 10 90 * - Docs - |docs| |docs_check| * - Tests - |actions_linux| |actions_windows| |actions_macos| |coveralls| * - PyPI - |pypi-version| |supported-versions| |supported-implementations| |wheel| * - Anaconda - |conda-version| |conda-platform| * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| .. |docs| image:: https://img.shields.io/readthedocs/enum_tools/latest?logo=read-the-docs :target: https://enum_tools.readthedocs.io/en/latest :alt: Documentation Build Status .. |docs_check| image:: https://github.com/domdfcoding/enum_tools/workflows/Docs%20Check/badge.svg :target: https://github.com/domdfcoding/enum_tools/actions?query=workflow%3A%22Docs+Check%22 :alt: Docs Check Status .. |actions_linux| image:: https://github.com/domdfcoding/enum_tools/workflows/Linux/badge.svg :target: https://github.com/domdfcoding/enum_tools/actions?query=workflow%3A%22Linux%22 :alt: Linux Test Status .. |actions_windows| image:: https://github.com/domdfcoding/enum_tools/workflows/Windows/badge.svg :target: https://github.com/domdfcoding/enum_tools/actions?query=workflow%3A%22Windows%22 :alt: Windows Test Status .. |actions_macos| image:: https://github.com/domdfcoding/enum_tools/workflows/macOS/badge.svg :target: https://github.com/domdfcoding/enum_tools/actions?query=workflow%3A%22macOS%22 :alt: macOS Test Status .. |actions_flake8| image:: https://github.com/domdfcoding/enum_tools/workflows/Flake8/badge.svg :target: https://github.com/domdfcoding/enum_tools/actions?query=workflow%3A%22Flake8%22 :alt: Flake8 Status .. |actions_mypy| image:: https://github.com/domdfcoding/enum_tools/workflows/mypy/badge.svg :target: https://github.com/domdfcoding/enum_tools/actions?query=workflow%3A%22mypy%22 :alt: mypy status .. |requires| image:: https://dependency-dash.herokuapp.com/github/domdfcoding/enum_tools/badge.svg :target: https://dependency-dash.herokuapp.com/github/domdfcoding/enum_tools/ :alt: Requirements Status .. |coveralls| image:: https://img.shields.io/coveralls/github/domdfcoding/enum_tools/master?logo=coveralls :target: https://coveralls.io/github/domdfcoding/enum_tools?branch=master :alt: Coverage .. |codefactor| image:: https://img.shields.io/codefactor/grade/github/domdfcoding/enum_tools?logo=codefactor :target: https://www.codefactor.io/repository/github/domdfcoding/enum_tools :alt: CodeFactor Grade .. |pypi-version| image:: https://img.shields.io/pypi/v/enum_tools :target: https://pypi.org/project/enum_tools/ :alt: PyPI - Package Version .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/enum_tools?logo=python&logoColor=white :target: https://pypi.org/project/enum_tools/ :alt: PyPI - Supported Python Versions .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/enum_tools :target: https://pypi.org/project/enum_tools/ :alt: PyPI - Supported Implementations .. |wheel| image:: https://img.shields.io/pypi/wheel/enum_tools :target: https://pypi.org/project/enum_tools/ :alt: PyPI - Wheel .. |conda-version| image:: https://img.shields.io/conda/v/domdfcoding/enum_tools?logo=anaconda :target: https://anaconda.org/domdfcoding/enum_tools :alt: Conda - Package Version .. |conda-platform| image:: https://img.shields.io/conda/pn/domdfcoding/enum_tools?label=conda%7Cplatform :target: https://anaconda.org/domdfcoding/enum_tools :alt: Conda - Platform .. |license| image:: https://img.shields.io/github/license/domdfcoding/enum_tools :target: https://github.com/domdfcoding/enum_tools/blob/master/LICENSE :alt: License .. |language| image:: https://img.shields.io/github/languages/top/domdfcoding/enum_tools :alt: GitHub top language .. |commits-since| image:: https://img.shields.io/github/commits-since/domdfcoding/enum_tools/v0.9.0 :target: https://github.com/domdfcoding/enum_tools/pulse :alt: GitHub commits since tagged version .. |commits-latest| image:: https://img.shields.io/github/last-commit/domdfcoding/enum_tools :target: https://github.com/domdfcoding/enum_tools/commit/master :alt: GitHub last commit .. |maintained| image:: https://img.shields.io/maintenance/yes/2022 :alt: Maintenance .. |pypi-downloads| image:: https://img.shields.io/pypi/dm/enum_tools :target: https://pypi.org/project/enum_tools/ :alt: PyPI - Downloads .. end shields This library provides the following: #. ``enum_tools.autoenum`` -- A `Sphinx `_ extension to document Enums better than ``autoclass`` can currently. #. ``@enum_tools.documentation.document_enum`` -- A decorator to add docstrings to ``Enum`` members from a comment at the end of the line. #. ``enum_tools.custom_enums`` -- Additional ``Enum`` classes with different functionality. Installation -------------- .. start installation ``enum_tools`` can be installed from PyPI or Anaconda. To install with ``pip``: .. code-block:: bash $ python -m pip install enum_tools To install with ``conda``: * First add the required channels .. code-block:: bash $ conda config --add channels https://conda.anaconda.org/conda-forge $ conda config --add channels https://conda.anaconda.org/domdfcoding * Then install .. code-block:: bash $ conda install enum_tools .. end installation Further Reading ----------------------- #. https://docs.python.org/3/library/enum.html #. `Is it possible to define a class constant inside an Enum? `_ #. `Enums with Attributes `_ #. `When should I subclass EnumMeta instead of Enum? `_ PK x?T (enum_tools-0.9.0.dist-info/top_level.txtenum_tools PK x?TS"enum_tools-0.9.0.dist-info/LICENSE GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. PK x?T+enum_tools-0.9.0.dist-info/entry_points.txtPK x?T`C@f!enum_tools-0.9.0.dist-info/RECORDenum_tools/demo.py,sha256=om043QTT5AG2GIQB3k3_L6IZiZ45xKoWRYL6ehH7sl8,1711 enum_tools/custom_enums.py,sha256=bM3RtKmnzW9AkTp9Oo1qGjfboDZ31GR0dlHybt4P9zE,6805 enum_tools/utils.py,sha256=JYx3WpZy0VO0S9X5gL1k6mqkJn608zoI57aPDwiTbEk,2683 enum_tools/__init__.py,sha256=WSCaBUcmraSmbth0p8GgULpF2JyQSlZ_Njpm6F-GaeI,1568 enum_tools/autoenum.py,sha256=kqfTsxa7j9BlOmuKcCUDm25UatX8_B-meuzAPRbND6M,19348 enum_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 enum_tools/documentation.py,sha256=PjVeqKh1X6AX0_N3Xv2L3T2YmqV1AcQAZ7m354uK-Sc,10949 enum_tools-0.9.0.dist-info/WHEEL,sha256=Z8ApXUcOYK5VMz9yebTyUF13KAMNT6RSnd-lLcivEVA,84 enum_tools-0.9.0.dist-info/METADATA,sha256=VWqInwX1IjE_C8mgoMqRPEibiiWFmsgdasHmUT6Z5CM,7936 enum_tools-0.9.0.dist-info/top_level.txt,sha256=1FDRSlXaFxq9RMSheU5XWymCg8QtpKQvnuOu5SRz4cw,11 enum_tools-0.9.0.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652 enum_tools-0.9.0.dist-info/entry_points.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 enum_tools-0.9.0.dist-info/RECORD,, PKsw?T4Ooenum_tools/demo.pyPKsw?Trenum_tools/custom_enums.pyPKsw?TP_x { { !enum_tools/utils.pyPKsw?TxؗV  X,enum_tools/__init__.pyPKsw?T] ȔKK2enum_tools/autoenum.pyPKsw?Tt~enum_tools/py.typedPKsw?T+$**~enum_tools/documentation.pyPK x?TTT enum_tools-0.9.0.dist-info/WHEELPK x?Td,#5enum_tools-0.9.0.dist-info/METADATAPK x?T (venum_tools-0.9.0.dist-info/top_level.txtPK x?TS"enum_tools-0.9.0.dist-info/LICENSEPK x?T+enum_tools-0.9.0.dist-info/entry_points.txtPK x?T`C@f!4enum_tools-0.9.0.dist-info/RECORDPK