diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f99008..8c3c3c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,10 +83,22 @@ jobs: - name: Publish run: | twine upload dist/$PROJECT_NAME-$VERSION.tar.gz -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} --non-interactive --skip-existing --disable-progress-bar - epythet make . github - name: Push Changes run: pack check-in "**CI** Formatted code + Updated version number and documentation. [skip ci]" --auto-choose-default-action --bypass-docstring-validation --bypass-tests --bypass-code-formatting --verbose - name: Tag Repository run: isee tag-repo $VERSION + + github-pages: + name: Publish GitHub Pages + if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)" + needs: publish + runs-on: ubuntu-latest + steps: + - uses: i2mint/epythet/actions/publish-github-pages@master + with: + # IMPORTANT Note: You don't need to specify GITHUB_TOKEN in your repo secrets. + # GITHUB_TOKEN is special & automatically provided to your workflow run. + github-token: ${{ secrets.GITHUB_TOKEN }} + ignore: "tests/,scrap/,examples/" diff --git a/.gitignore b/.gitignore index 13e0ad4..79cf58f 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,6 @@ venv.bak/ # PyCharm .idea + +# epythet regenerates the Sphinx sources on every build +docsrc/ diff --git a/README.md b/README.md index f46aea4..0bc1aa7 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,16 @@ to bend your interface with data to your will. [More examples](#more-examples) will give you a taste of how you can adapt the three main aspects of storage (persistence, serialization, and indexing) to your needs. + +# For AI agents + +`py2store` publishes its documentation in forms made for coding agents. If you are one, start here. + +**The documentation, machine-readable**: [`llms.txt`](https://i2mint.github.io/py2store/llms.txt) indexes every page; [`py2store.md`](https://i2mint.github.io/py2store/py2store.md) is the whole documentation in one file; every page has a `.md` twin; [`objects.inv`](https://i2mint.github.io/py2store/objects.inv) maps symbols to URLs. + +If you like writing your own code, the rest of this README is written for you, starting at [Contents](#contents). + + # Contents - [py2store](#py2store) diff --git a/docs/.buildinfo b/docs/.buildinfo deleted file mode 100644 index 21c13c2..0000000 --- a/docs/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: c89c4b51cb202bbf3cc4eb9f92e81131 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/docs/_modules/configparser.html b/docs/_modules/configparser.html deleted file mode 100644 index 5842ef9..0000000 --- a/docs/_modules/configparser.html +++ /dev/null @@ -1,1564 +0,0 @@ - - - - - - - - configparser — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for configparser

-"""Configuration file parser.
-
-A configuration file consists of sections, lead by a "[section]" header,
-and followed by "name: value" entries, with continuations and such in
-the style of RFC 822.
-
-Intrinsic defaults can be specified by passing them into the
-ConfigParser constructor as a dictionary.
-
-class:
-
-ConfigParser -- responsible for parsing a list of
-                    configuration files, and managing the parsed database.
-
-    methods:
-
-    __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
-             delimiters=('=', ':'), comment_prefixes=('#', ';'),
-             inline_comment_prefixes=None, strict=True,
-             empty_lines_in_values=True, default_section='DEFAULT',
-             interpolation=<unset>, converters=<unset>):
-        Create the parser. When `defaults' is given, it is initialized into the
-        dictionary or intrinsic defaults. The keys must be strings, the values
-        must be appropriate for %()s string interpolation.
-
-        When `dict_type' is given, it will be used to create the dictionary
-        objects for the list of sections, for the options within a section, and
-        for the default values.
-
-        When `delimiters' is given, it will be used as the set of substrings
-        that divide keys from values.
-
-        When `comment_prefixes' is given, it will be used as the set of
-        substrings that prefix comments in empty lines. Comments can be
-        indented.
-
-        When `inline_comment_prefixes' is given, it will be used as the set of
-        substrings that prefix comments in non-empty lines.
-
-        When `strict` is True, the parser won't allow for any section or option
-        duplicates while reading from a single source (file, string or
-        dictionary). Default is True.
-
-        When `empty_lines_in_values' is False (default: True), each empty line
-        marks the end of an option. Otherwise, internal empty lines of
-        a multiline option are kept as part of the value.
-
-        When `allow_no_value' is True (default: False), options without
-        values are accepted; the value presented for these is None.
-
-        When `default_section' is given, the name of the special section is
-        named accordingly. By default it is called ``"DEFAULT"`` but this can
-        be customized to point to any other valid section name. Its current
-        value can be retrieved using the ``parser_instance.default_section``
-        attribute and may be modified at runtime.
-
-        When `interpolation` is given, it should be an Interpolation subclass
-        instance. It will be used as the handler for option value
-        pre-processing when using getters. RawConfigParser objects don't do
-        any sort of interpolation, whereas ConfigParser uses an instance of
-        BasicInterpolation. The library also provides a ``zc.buildbot``
-        inspired ExtendedInterpolation implementation.
-
-        When `converters` is given, it should be a dictionary where each key
-        represents the name of a type converter and each value is a callable
-        implementing the conversion from string to the desired datatype. Every
-        converter gets its corresponding get*() method on the parser object and
-        section proxies.
-
-    sections()
-        Return all the configuration section names, sans DEFAULT.
-
-    has_section(section)
-        Return whether the given section exists.
-
-    has_option(section, option)
-        Return whether the given option exists in the given section.
-
-    options(section)
-        Return list of configuration options for the named section.
-
-    read(filenames, encoding=None)
-        Read and parse the iterable of named configuration files, given by
-        name.  A single filename is also allowed.  Non-existing files
-        are ignored.  Return list of successfully read files.
-
-    read_file(f, filename=None)
-        Read and parse one configuration file, given as a file object.
-        The filename defaults to f.name; it is only used in error
-        messages (if f has no `name' attribute, the string `<???>' is used).
-
-    read_string(string)
-        Read configuration from a given string.
-
-    read_dict(dictionary)
-        Read configuration from a dictionary. Keys are section names,
-        values are dictionaries with keys and values that should be present
-        in the section. If the used dictionary type preserves order, sections
-        and their keys will be added in order. Values are automatically
-        converted to strings.
-
-    get(section, option, raw=False, vars=None, fallback=_UNSET)
-        Return a string value for the named option.  All % interpolations are
-        expanded in the return values, based on the defaults passed into the
-        constructor and the DEFAULT section.  Additional substitutions may be
-        provided using the `vars' argument, which must be a dictionary whose
-        contents override any pre-existing defaults. If `option' is a key in
-        `vars', the value from `vars' is used.
-
-    getint(section, options, raw=False, vars=None, fallback=_UNSET)
-        Like get(), but convert value to an integer.
-
-    getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
-        Like get(), but convert value to a float.
-
-    getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
-        Like get(), but convert value to a boolean (currently case
-        insensitively defined as 0, false, no, off for False, and 1, true,
-        yes, on for True).  Returns False or True.
-
-    items(section=_UNSET, raw=False, vars=None)
-        If section is given, return a list of tuples with (name, value) for
-        each option in the section. Otherwise, return a list of tuples with
-        (section_name, section_proxy) for each section, including DEFAULTSECT.
-
-    remove_section(section)
-        Remove the given file section and all its options.
-
-    remove_option(section, option)
-        Remove the given option from the given section.
-
-    set(section, option, value)
-        Set the given option.
-
-    write(fp, space_around_delimiters=True)
-        Write the configuration state in .ini format. If
-        `space_around_delimiters' is True (the default), delimiters
-        between keys and values are surrounded by spaces.
-"""
-
-from collections.abc import MutableMapping
-from collections import ChainMap as _ChainMap
-import functools
-import io
-import itertools
-import os
-import re
-import sys
-import warnings
-
-__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
-           "NoOptionError", "InterpolationError", "InterpolationDepthError",
-           "InterpolationMissingOptionError", "InterpolationSyntaxError",
-           "ParsingError", "MissingSectionHeaderError",
-           "ConfigParser", "SafeConfigParser", "RawConfigParser",
-           "Interpolation", "BasicInterpolation",  "ExtendedInterpolation",
-           "LegacyInterpolation", "SectionProxy", "ConverterMapping",
-           "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
-
-_default_dict = dict
-DEFAULTSECT = "DEFAULT"
-
-MAX_INTERPOLATION_DEPTH = 10
-
-
-
-# exception classes
-class Error(Exception):
-    """Base class for ConfigParser exceptions."""
-
-    def __init__(self, msg=''):
-        self.message = msg
-        Exception.__init__(self, msg)
-
-    def __repr__(self):
-        return self.message
-
-    __str__ = __repr__
-
-
-class NoSectionError(Error):
-    """Raised when no section matches a requested option."""
-
-    def __init__(self, section):
-        Error.__init__(self, 'No section: %r' % (section,))
-        self.section = section
-        self.args = (section, )
-
-
-class DuplicateSectionError(Error):
-    """Raised when a section is repeated in an input source.
-
-    Possible repetitions that raise this exception are: multiple creation
-    using the API or in strict parsers when a section is found more than once
-    in a single input file, string or dictionary.
-    """
-
-    def __init__(self, section, source=None, lineno=None):
-        msg = [repr(section), " already exists"]
-        if source is not None:
-            message = ["While reading from ", repr(source)]
-            if lineno is not None:
-                message.append(" [line {0:2d}]".format(lineno))
-            message.append(": section ")
-            message.extend(msg)
-            msg = message
-        else:
-            msg.insert(0, "Section ")
-        Error.__init__(self, "".join(msg))
-        self.section = section
-        self.source = source
-        self.lineno = lineno
-        self.args = (section, source, lineno)
-
-
-class DuplicateOptionError(Error):
-    """Raised by strict parsers when an option is repeated in an input source.
-
-    Current implementation raises this exception only when an option is found
-    more than once in a single file, string or dictionary.
-    """
-
-    def __init__(self, section, option, source=None, lineno=None):
-        msg = [repr(option), " in section ", repr(section),
-               " already exists"]
-        if source is not None:
-            message = ["While reading from ", repr(source)]
-            if lineno is not None:
-                message.append(" [line {0:2d}]".format(lineno))
-            message.append(": option ")
-            message.extend(msg)
-            msg = message
-        else:
-            msg.insert(0, "Option ")
-        Error.__init__(self, "".join(msg))
-        self.section = section
-        self.option = option
-        self.source = source
-        self.lineno = lineno
-        self.args = (section, option, source, lineno)
-
-
-class NoOptionError(Error):
-    """A requested option was not found."""
-
-    def __init__(self, option, section):
-        Error.__init__(self, "No option %r in section: %r" %
-                       (option, section))
-        self.option = option
-        self.section = section
-        self.args = (option, section)
-
-
-class InterpolationError(Error):
-    """Base class for interpolation-related exceptions."""
-
-    def __init__(self, option, section, msg):
-        Error.__init__(self, msg)
-        self.option = option
-        self.section = section
-        self.args = (option, section, msg)
-
-
-class InterpolationMissingOptionError(InterpolationError):
-    """A string substitution required a setting which was not available."""
-
-    def __init__(self, option, section, rawval, reference):
-        msg = ("Bad value substitution: option {!r} in section {!r} contains "
-               "an interpolation key {!r} which is not a valid option name. "
-               "Raw value: {!r}".format(option, section, reference, rawval))
-        InterpolationError.__init__(self, option, section, msg)
-        self.reference = reference
-        self.args = (option, section, rawval, reference)
-
-
-class InterpolationSyntaxError(InterpolationError):
-    """Raised when the source text contains invalid syntax.
-
-    Current implementation raises this exception when the source text into
-    which substitutions are made does not conform to the required syntax.
-    """
-
-
-class InterpolationDepthError(InterpolationError):
-    """Raised when substitutions are nested too deeply."""
-
-    def __init__(self, option, section, rawval):
-        msg = ("Recursion limit exceeded in value substitution: option {!r} "
-               "in section {!r} contains an interpolation key which "
-               "cannot be substituted in {} steps. Raw value: {!r}"
-               "".format(option, section, MAX_INTERPOLATION_DEPTH,
-                         rawval))
-        InterpolationError.__init__(self, option, section, msg)
-        self.args = (option, section, rawval)
-
-
-class ParsingError(Error):
-    """Raised when a configuration file does not follow legal syntax."""
-
-    def __init__(self, source=None, filename=None):
-        # Exactly one of `source'/`filename' arguments has to be given.
-        # `filename' kept for compatibility.
-        if filename and source:
-            raise ValueError("Cannot specify both `filename' and `source'. "
-                             "Use `source'.")
-        elif not filename and not source:
-            raise ValueError("Required argument `source' not given.")
-        elif filename:
-            source = filename
-        Error.__init__(self, 'Source contains parsing errors: %r' % source)
-        self.source = source
-        self.errors = []
-        self.args = (source, )
-
-    @property
-    def filename(self):
-        """Deprecated, use `source'."""
-        warnings.warn(
-            "The 'filename' attribute will be removed in future versions.  "
-            "Use 'source' instead.",
-            DeprecationWarning, stacklevel=2
-        )
-        return self.source
-
-    @filename.setter
-    def filename(self, value):
-        """Deprecated, user `source'."""
-        warnings.warn(
-            "The 'filename' attribute will be removed in future versions.  "
-            "Use 'source' instead.",
-            DeprecationWarning, stacklevel=2
-        )
-        self.source = value
-
-    def append(self, lineno, line):
-        self.errors.append((lineno, line))
-        self.message += '\n\t[line %2d]: %s' % (lineno, line)
-
-
-class MissingSectionHeaderError(ParsingError):
-    """Raised when a key-value pair is found before any section header."""
-
-    def __init__(self, filename, lineno, line):
-        Error.__init__(
-            self,
-            'File contains no section headers.\nfile: %r, line: %d\n%r' %
-            (filename, lineno, line))
-        self.source = filename
-        self.lineno = lineno
-        self.line = line
-        self.args = (filename, lineno, line)
-
-
-# Used in parser getters to indicate the default behaviour when a specific
-# option is not found it to raise an exception. Created to enable `None' as
-# a valid fallback value.
-_UNSET = object()
-
-
-class Interpolation:
-    """Dummy interpolation that passes the value through with no changes."""
-
-    def before_get(self, parser, section, option, value, defaults):
-        return value
-
-    def before_set(self, parser, section, option, value):
-        return value
-
-    def before_read(self, parser, section, option, value):
-        return value
-
-    def before_write(self, parser, section, option, value):
-        return value
-
-
-class BasicInterpolation(Interpolation):
-    """Interpolation as implemented in the classic ConfigParser.
-
-    The option values can contain format strings which refer to other values in
-    the same section, or values in the special default section.
-
-    For example:
-
-        something: %(dir)s/whatever
-
-    would resolve the "%(dir)s" to the value of dir.  All reference
-    expansions are done late, on demand. If a user needs to use a bare % in
-    a configuration file, she can escape it by writing %%. Other % usage
-    is considered a user error and raises `InterpolationSyntaxError'."""
-
-    _KEYCRE = re.compile(r"%\(([^)]+)\)s")
-
-    def before_get(self, parser, section, option, value, defaults):
-        L = []
-        self._interpolate_some(parser, option, L, value, section, defaults, 1)
-        return ''.join(L)
-
-    def before_set(self, parser, section, option, value):
-        tmp_value = value.replace('%%', '') # escaped percent signs
-        tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
-        if '%' in tmp_value:
-            raise ValueError("invalid interpolation syntax in %r at "
-                             "position %d" % (value, tmp_value.find('%')))
-        return value
-
-    def _interpolate_some(self, parser, option, accum, rest, section, map,
-                          depth):
-        rawval = parser.get(section, option, raw=True, fallback=rest)
-        if depth > MAX_INTERPOLATION_DEPTH:
-            raise InterpolationDepthError(option, section, rawval)
-        while rest:
-            p = rest.find("%")
-            if p < 0:
-                accum.append(rest)
-                return
-            if p > 0:
-                accum.append(rest[:p])
-                rest = rest[p:]
-            # p is no longer used
-            c = rest[1:2]
-            if c == "%":
-                accum.append("%")
-                rest = rest[2:]
-            elif c == "(":
-                m = self._KEYCRE.match(rest)
-                if m is None:
-                    raise InterpolationSyntaxError(option, section,
-                        "bad interpolation variable reference %r" % rest)
-                var = parser.optionxform(m.group(1))
-                rest = rest[m.end():]
-                try:
-                    v = map[var]
-                except KeyError:
-                    raise InterpolationMissingOptionError(
-                        option, section, rawval, var) from None
-                if "%" in v:
-                    self._interpolate_some(parser, option, accum, v,
-                                           section, map, depth + 1)
-                else:
-                    accum.append(v)
-            else:
-                raise InterpolationSyntaxError(
-                    option, section,
-                    "'%%' must be followed by '%%' or '(', "
-                    "found: %r" % (rest,))
-
-
-class ExtendedInterpolation(Interpolation):
-    """Advanced variant of interpolation, supports the syntax used by
-    `zc.buildout'. Enables interpolation between sections."""
-
-    _KEYCRE = re.compile(r"\$\{([^}]+)\}")
-
-    def before_get(self, parser, section, option, value, defaults):
-        L = []
-        self._interpolate_some(parser, option, L, value, section, defaults, 1)
-        return ''.join(L)
-
-    def before_set(self, parser, section, option, value):
-        tmp_value = value.replace('$$', '') # escaped dollar signs
-        tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
-        if '$' in tmp_value:
-            raise ValueError("invalid interpolation syntax in %r at "
-                             "position %d" % (value, tmp_value.find('$')))
-        return value
-
-    def _interpolate_some(self, parser, option, accum, rest, section, map,
-                          depth):
-        rawval = parser.get(section, option, raw=True, fallback=rest)
-        if depth > MAX_INTERPOLATION_DEPTH:
-            raise InterpolationDepthError(option, section, rawval)
-        while rest:
-            p = rest.find("$")
-            if p < 0:
-                accum.append(rest)
-                return
-            if p > 0:
-                accum.append(rest[:p])
-                rest = rest[p:]
-            # p is no longer used
-            c = rest[1:2]
-            if c == "$":
-                accum.append("$")
-                rest = rest[2:]
-            elif c == "{":
-                m = self._KEYCRE.match(rest)
-                if m is None:
-                    raise InterpolationSyntaxError(option, section,
-                        "bad interpolation variable reference %r" % rest)
-                path = m.group(1).split(':')
-                rest = rest[m.end():]
-                sect = section
-                opt = option
-                try:
-                    if len(path) == 1:
-                        opt = parser.optionxform(path[0])
-                        v = map[opt]
-                    elif len(path) == 2:
-                        sect = path[0]
-                        opt = parser.optionxform(path[1])
-                        v = parser.get(sect, opt, raw=True)
-                    else:
-                        raise InterpolationSyntaxError(
-                            option, section,
-                            "More than one ':' found: %r" % (rest,))
-                except (KeyError, NoSectionError, NoOptionError):
-                    raise InterpolationMissingOptionError(
-                        option, section, rawval, ":".join(path)) from None
-                if "$" in v:
-                    self._interpolate_some(parser, opt, accum, v, sect,
-                                           dict(parser.items(sect, raw=True)),
-                                           depth + 1)
-                else:
-                    accum.append(v)
-            else:
-                raise InterpolationSyntaxError(
-                    option, section,
-                    "'$' must be followed by '$' or '{', "
-                    "found: %r" % (rest,))
-
-
-class LegacyInterpolation(Interpolation):
-    """Deprecated interpolation used in old versions of ConfigParser.
-    Use BasicInterpolation or ExtendedInterpolation instead."""
-
-    _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
-
-    def before_get(self, parser, section, option, value, vars):
-        rawval = value
-        depth = MAX_INTERPOLATION_DEPTH
-        while depth:                    # Loop through this until it's done
-            depth -= 1
-            if value and "%(" in value:
-                replace = functools.partial(self._interpolation_replace,
-                                            parser=parser)
-                value = self._KEYCRE.sub(replace, value)
-                try:
-                    value = value % vars
-                except KeyError as e:
-                    raise InterpolationMissingOptionError(
-                        option, section, rawval, e.args[0]) from None
-            else:
-                break
-        if value and "%(" in value:
-            raise InterpolationDepthError(option, section, rawval)
-        return value
-
-    def before_set(self, parser, section, option, value):
-        return value
-
-    @staticmethod
-    def _interpolation_replace(match, parser):
-        s = match.group(1)
-        if s is None:
-            return match.group()
-        else:
-            return "%%(%s)s" % parser.optionxform(s)
-
-
-class RawConfigParser(MutableMapping):
-    """ConfigParser that does not do interpolation."""
-
-    # Regular expressions for parsing section headers and options
-    _SECT_TMPL = r"""
-        \[                                 # [
-        (?P<header>[^]]+)                  # very permissive!
-        \]                                 # ]
-        """
-    _OPT_TMPL = r"""
-        (?P<option>.*?)                    # very permissive!
-        \s*(?P<vi>{delim})\s*              # any number of space/tab,
-                                           # followed by any of the
-                                           # allowed delimiters,
-                                           # followed by any space/tab
-        (?P<value>.*)$                     # everything up to eol
-        """
-    _OPT_NV_TMPL = r"""
-        (?P<option>.*?)                    # very permissive!
-        \s*(?:                             # any number of space/tab,
-        (?P<vi>{delim})\s*                 # optionally followed by
-                                           # any of the allowed
-                                           # delimiters, followed by any
-                                           # space/tab
-        (?P<value>.*))?$                   # everything up to eol
-        """
-    # Interpolation algorithm to be used if the user does not specify another
-    _DEFAULT_INTERPOLATION = Interpolation()
-    # Compiled regular expression for matching sections
-    SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
-    # Compiled regular expression for matching options with typical separators
-    OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE)
-    # Compiled regular expression for matching options with optional values
-    # delimited using typical separators
-    OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
-    # Compiled regular expression for matching leading whitespace in a line
-    NONSPACECRE = re.compile(r"\S")
-    # Possible boolean values in the configuration.
-    BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
-                      '0': False, 'no': False, 'false': False, 'off': False}
-
-    def __init__(self, defaults=None, dict_type=_default_dict,
-                 allow_no_value=False, *, delimiters=('=', ':'),
-                 comment_prefixes=('#', ';'), inline_comment_prefixes=None,
-                 strict=True, empty_lines_in_values=True,
-                 default_section=DEFAULTSECT,
-                 interpolation=_UNSET, converters=_UNSET):
-
-        self._dict = dict_type
-        self._sections = self._dict()
-        self._defaults = self._dict()
-        self._converters = ConverterMapping(self)
-        self._proxies = self._dict()
-        self._proxies[default_section] = SectionProxy(self, default_section)
-        self._delimiters = tuple(delimiters)
-        if delimiters == ('=', ':'):
-            self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
-        else:
-            d = "|".join(re.escape(d) for d in delimiters)
-            if allow_no_value:
-                self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d),
-                                          re.VERBOSE)
-            else:
-                self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
-                                          re.VERBOSE)
-        self._comment_prefixes = tuple(comment_prefixes or ())
-        self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
-        self._strict = strict
-        self._allow_no_value = allow_no_value
-        self._empty_lines_in_values = empty_lines_in_values
-        self.default_section=default_section
-        self._interpolation = interpolation
-        if self._interpolation is _UNSET:
-            self._interpolation = self._DEFAULT_INTERPOLATION
-        if self._interpolation is None:
-            self._interpolation = Interpolation()
-        if converters is not _UNSET:
-            self._converters.update(converters)
-        if defaults:
-            self._read_defaults(defaults)
-
-    def defaults(self):
-        return self._defaults
-
-    def sections(self):
-        """Return a list of section names, excluding [DEFAULT]"""
-        # self._sections will never have [DEFAULT] in it
-        return list(self._sections.keys())
-
-    def add_section(self, section):
-        """Create a new section in the configuration.
-
-        Raise DuplicateSectionError if a section by the specified name
-        already exists. Raise ValueError if name is DEFAULT.
-        """
-        if section == self.default_section:
-            raise ValueError('Invalid section name: %r' % section)
-
-        if section in self._sections:
-            raise DuplicateSectionError(section)
-        self._sections[section] = self._dict()
-        self._proxies[section] = SectionProxy(self, section)
-
-    def has_section(self, section):
-        """Indicate whether the named section is present in the configuration.
-
-        The DEFAULT section is not acknowledged.
-        """
-        return section in self._sections
-
-    def options(self, section):
-        """Return a list of option names for the given section name."""
-        try:
-            opts = self._sections[section].copy()
-        except KeyError:
-            raise NoSectionError(section) from None
-        opts.update(self._defaults)
-        return list(opts.keys())
-
-    def read(self, filenames, encoding=None):
-        """Read and parse a filename or an iterable of filenames.
-
-        Files that cannot be opened are silently ignored; this is
-        designed so that you can specify an iterable of potential
-        configuration file locations (e.g. current directory, user's
-        home directory, systemwide directory), and all existing
-        configuration files in the iterable will be read.  A single
-        filename may also be given.
-
-        Return list of successfully read files.
-        """
-        if isinstance(filenames, (str, bytes, os.PathLike)):
-            filenames = [filenames]
-        read_ok = []
-        for filename in filenames:
-            try:
-                with open(filename, encoding=encoding) as fp:
-                    self._read(fp, filename)
-            except OSError:
-                continue
-            if isinstance(filename, os.PathLike):
-                filename = os.fspath(filename)
-            read_ok.append(filename)
-        return read_ok
-
-    def read_file(self, f, source=None):
-        """Like read() but the argument must be a file-like object.
-
-        The `f' argument must be iterable, returning one line at a time.
-        Optional second argument is the `source' specifying the name of the
-        file being read. If not given, it is taken from f.name. If `f' has no
-        `name' attribute, `<???>' is used.
-        """
-        if source is None:
-            try:
-                source = f.name
-            except AttributeError:
-                source = '<???>'
-        self._read(f, source)
-
-    def read_string(self, string, source='<string>'):
-        """Read configuration from a given string."""
-        sfile = io.StringIO(string)
-        self.read_file(sfile, source)
-
-    def read_dict(self, dictionary, source='<dict>'):
-        """Read configuration from a dictionary.
-
-        Keys are section names, values are dictionaries with keys and values
-        that should be present in the section. If the used dictionary type
-        preserves order, sections and their keys will be added in order.
-
-        All types held in the dictionary are converted to strings during
-        reading, including section names, option names and keys.
-
-        Optional second argument is the `source' specifying the name of the
-        dictionary being read.
-        """
-        elements_added = set()
-        for section, keys in dictionary.items():
-            section = str(section)
-            try:
-                self.add_section(section)
-            except (DuplicateSectionError, ValueError):
-                if self._strict and section in elements_added:
-                    raise
-            elements_added.add(section)
-            for key, value in keys.items():
-                key = self.optionxform(str(key))
-                if value is not None:
-                    value = str(value)
-                if self._strict and (section, key) in elements_added:
-                    raise DuplicateOptionError(section, key, source)
-                elements_added.add((section, key))
-                self.set(section, key, value)
-
-    def readfp(self, fp, filename=None):
-        """Deprecated, use read_file instead."""
-        warnings.warn(
-            "This method will be removed in future versions.  "
-            "Use 'parser.read_file()' instead.",
-            DeprecationWarning, stacklevel=2
-        )
-        self.read_file(fp, source=filename)
-
-    def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
-        """Get an option value for a given section.
-
-        If `vars' is provided, it must be a dictionary. The option is looked up
-        in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
-        If the key is not found and `fallback' is provided, it is used as
-        a fallback value. `None' can be provided as a `fallback' value.
-
-        If interpolation is enabled and the optional argument `raw' is False,
-        all interpolations are expanded in the return values.
-
-        Arguments `raw', `vars', and `fallback' are keyword only.
-
-        The section DEFAULT is special.
-        """
-        try:
-            d = self._unify_values(section, vars)
-        except NoSectionError:
-            if fallback is _UNSET:
-                raise
-            else:
-                return fallback
-        option = self.optionxform(option)
-        try:
-            value = d[option]
-        except KeyError:
-            if fallback is _UNSET:
-                raise NoOptionError(option, section)
-            else:
-                return fallback
-
-        if raw or value is None:
-            return value
-        else:
-            return self._interpolation.before_get(self, section, option, value,
-                                                  d)
-
-    def _get(self, section, conv, option, **kwargs):
-        return conv(self.get(section, option, **kwargs))
-
-    def _get_conv(self, section, option, conv, *, raw=False, vars=None,
-                  fallback=_UNSET, **kwargs):
-        try:
-            return self._get(section, conv, option, raw=raw, vars=vars,
-                             **kwargs)
-        except (NoSectionError, NoOptionError):
-            if fallback is _UNSET:
-                raise
-            return fallback
-
-    # getint, getfloat and getboolean provided directly for backwards compat
-    def getint(self, section, option, *, raw=False, vars=None,
-               fallback=_UNSET, **kwargs):
-        return self._get_conv(section, option, int, raw=raw, vars=vars,
-                              fallback=fallback, **kwargs)
-
-    def getfloat(self, section, option, *, raw=False, vars=None,
-                 fallback=_UNSET, **kwargs):
-        return self._get_conv(section, option, float, raw=raw, vars=vars,
-                              fallback=fallback, **kwargs)
-
-    def getboolean(self, section, option, *, raw=False, vars=None,
-                   fallback=_UNSET, **kwargs):
-        return self._get_conv(section, option, self._convert_to_boolean,
-                              raw=raw, vars=vars, fallback=fallback, **kwargs)
-
-    def items(self, section=_UNSET, raw=False, vars=None):
-        """Return a list of (name, value) tuples for each option in a section.
-
-        All % interpolations are expanded in the return values, based on the
-        defaults passed into the constructor, unless the optional argument
-        `raw' is true.  Additional substitutions may be provided using the
-        `vars' argument, which must be a dictionary whose contents overrides
-        any pre-existing defaults.
-
-        The section DEFAULT is special.
-        """
-        if section is _UNSET:
-            return super().items()
-        d = self._defaults.copy()
-        try:
-            d.update(self._sections[section])
-        except KeyError:
-            if section != self.default_section:
-                raise NoSectionError(section)
-        orig_keys = list(d.keys())
-        # Update with the entry specific variables
-        if vars:
-            for key, value in vars.items():
-                d[self.optionxform(key)] = value
-        value_getter = lambda option: self._interpolation.before_get(self,
-            section, option, d[option], d)
-        if raw:
-            value_getter = lambda option: d[option]
-        return [(option, value_getter(option)) for option in orig_keys]
-
-    def popitem(self):
-        """Remove a section from the parser and return it as
-        a (section_name, section_proxy) tuple. If no section is present, raise
-        KeyError.
-
-        The section DEFAULT is never returned because it cannot be removed.
-        """
-        for key in self.sections():
-            value = self[key]
-            del self[key]
-            return key, value
-        raise KeyError
-
-    def optionxform(self, optionstr):
-        return optionstr.lower()
-
-    def has_option(self, section, option):
-        """Check for the existence of a given option in a given section.
-        If the specified `section' is None or an empty string, DEFAULT is
-        assumed. If the specified `section' does not exist, returns False."""
-        if not section or section == self.default_section:
-            option = self.optionxform(option)
-            return option in self._defaults
-        elif section not in self._sections:
-            return False
-        else:
-            option = self.optionxform(option)
-            return (option in self._sections[section]
-                    or option in self._defaults)
-
-    def set(self, section, option, value=None):
-        """Set an option."""
-        if value:
-            value = self._interpolation.before_set(self, section, option,
-                                                   value)
-        if not section or section == self.default_section:
-            sectdict = self._defaults
-        else:
-            try:
-                sectdict = self._sections[section]
-            except KeyError:
-                raise NoSectionError(section) from None
-        sectdict[self.optionxform(option)] = value
-
-    def write(self, fp, space_around_delimiters=True):
-        """Write an .ini-format representation of the configuration state.
-
-        If `space_around_delimiters' is True (the default), delimiters
-        between keys and values are surrounded by spaces.
-        """
-        if space_around_delimiters:
-            d = " {} ".format(self._delimiters[0])
-        else:
-            d = self._delimiters[0]
-        if self._defaults:
-            self._write_section(fp, self.default_section,
-                                    self._defaults.items(), d)
-        for section in self._sections:
-            self._write_section(fp, section,
-                                self._sections[section].items(), d)
-
-    def _write_section(self, fp, section_name, section_items, delimiter):
-        """Write a single section to the specified `fp'."""
-        fp.write("[{}]\n".format(section_name))
-        for key, value in section_items:
-            value = self._interpolation.before_write(self, section_name, key,
-                                                     value)
-            if value is not None or not self._allow_no_value:
-                value = delimiter + str(value).replace('\n', '\n\t')
-            else:
-                value = ""
-            fp.write("{}{}\n".format(key, value))
-        fp.write("\n")
-
-    def remove_option(self, section, option):
-        """Remove an option."""
-        if not section or section == self.default_section:
-            sectdict = self._defaults
-        else:
-            try:
-                sectdict = self._sections[section]
-            except KeyError:
-                raise NoSectionError(section) from None
-        option = self.optionxform(option)
-        existed = option in sectdict
-        if existed:
-            del sectdict[option]
-        return existed
-
-    def remove_section(self, section):
-        """Remove a file section."""
-        existed = section in self._sections
-        if existed:
-            del self._sections[section]
-            del self._proxies[section]
-        return existed
-
-    def __getitem__(self, key):
-        if key != self.default_section and not self.has_section(key):
-            raise KeyError(key)
-        return self._proxies[key]
-
-    def __setitem__(self, key, value):
-        # To conform with the mapping protocol, overwrites existing values in
-        # the section.
-        if key in self and self[key] is value:
-            return
-        # XXX this is not atomic if read_dict fails at any point. Then again,
-        # no update method in configparser is atomic in this implementation.
-        if key == self.default_section:
-            self._defaults.clear()
-        elif key in self._sections:
-            self._sections[key].clear()
-        self.read_dict({key: value})
-
-    def __delitem__(self, key):
-        if key == self.default_section:
-            raise ValueError("Cannot remove the default section.")
-        if not self.has_section(key):
-            raise KeyError(key)
-        self.remove_section(key)
-
-    def __contains__(self, key):
-        return key == self.default_section or self.has_section(key)
-
-    def __len__(self):
-        return len(self._sections) + 1 # the default section
-
-    def __iter__(self):
-        # XXX does it break when underlying container state changed?
-        return itertools.chain((self.default_section,), self._sections.keys())
-
-    def _read(self, fp, fpname):
-        """Parse a sectioned configuration file.
-
-        Each section in a configuration file contains a header, indicated by
-        a name in square brackets (`[]'), plus key/value options, indicated by
-        `name' and `value' delimited with a specific substring (`=' or `:' by
-        default).
-
-        Values can span multiple lines, as long as they are indented deeper
-        than the first line of the value. Depending on the parser's mode, blank
-        lines may be treated as parts of multiline values or ignored.
-
-        Configuration files may include comments, prefixed by specific
-        characters (`#' and `;' by default). Comments may appear on their own
-        in an otherwise empty line or may be entered in lines holding values or
-        section names.
-        """
-        elements_added = set()
-        cursect = None                        # None, or a dictionary
-        sectname = None
-        optname = None
-        lineno = 0
-        indent_level = 0
-        e = None                              # None, or an exception
-        for lineno, line in enumerate(fp, start=1):
-            comment_start = sys.maxsize
-            # strip inline comments
-            inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
-            while comment_start == sys.maxsize and inline_prefixes:
-                next_prefixes = {}
-                for prefix, index in inline_prefixes.items():
-                    index = line.find(prefix, index+1)
-                    if index == -1:
-                        continue
-                    next_prefixes[prefix] = index
-                    if index == 0 or (index > 0 and line[index-1].isspace()):
-                        comment_start = min(comment_start, index)
-                inline_prefixes = next_prefixes
-            # strip full line comments
-            for prefix in self._comment_prefixes:
-                if line.strip().startswith(prefix):
-                    comment_start = 0
-                    break
-            if comment_start == sys.maxsize:
-                comment_start = None
-            value = line[:comment_start].strip()
-            if not value:
-                if self._empty_lines_in_values:
-                    # add empty line to the value, but only if there was no
-                    # comment on the line
-                    if (comment_start is None and
-                        cursect is not None and
-                        optname and
-                        cursect[optname] is not None):
-                        cursect[optname].append('') # newlines added at join
-                else:
-                    # empty line marks end of value
-                    indent_level = sys.maxsize
-                continue
-            # continuation line?
-            first_nonspace = self.NONSPACECRE.search(line)
-            cur_indent_level = first_nonspace.start() if first_nonspace else 0
-            if (cursect is not None and optname and
-                cur_indent_level > indent_level):
-                cursect[optname].append(value)
-            # a section header or option header?
-            else:
-                indent_level = cur_indent_level
-                # is it a section header?
-                mo = self.SECTCRE.match(value)
-                if mo:
-                    sectname = mo.group('header')
-                    if sectname in self._sections:
-                        if self._strict and sectname in elements_added:
-                            raise DuplicateSectionError(sectname, fpname,
-                                                        lineno)
-                        cursect = self._sections[sectname]
-                        elements_added.add(sectname)
-                    elif sectname == self.default_section:
-                        cursect = self._defaults
-                    else:
-                        cursect = self._dict()
-                        self._sections[sectname] = cursect
-                        self._proxies[sectname] = SectionProxy(self, sectname)
-                        elements_added.add(sectname)
-                    # So sections can't start with a continuation line
-                    optname = None
-                # no section header in the file?
-                elif cursect is None:
-                    raise MissingSectionHeaderError(fpname, lineno, line)
-                # an option line?
-                else:
-                    mo = self._optcre.match(value)
-                    if mo:
-                        optname, vi, optval = mo.group('option', 'vi', 'value')
-                        if not optname:
-                            e = self._handle_error(e, fpname, lineno, line)
-                        optname = self.optionxform(optname.rstrip())
-                        if (self._strict and
-                            (sectname, optname) in elements_added):
-                            raise DuplicateOptionError(sectname, optname,
-                                                       fpname, lineno)
-                        elements_added.add((sectname, optname))
-                        # This check is fine because the OPTCRE cannot
-                        # match if it would set optval to None
-                        if optval is not None:
-                            optval = optval.strip()
-                            cursect[optname] = [optval]
-                        else:
-                            # valueless option handling
-                            cursect[optname] = None
-                    else:
-                        # a non-fatal parsing error occurred. set up the
-                        # exception but keep going. the exception will be
-                        # raised at the end of the file and will contain a
-                        # list of all bogus lines
-                        e = self._handle_error(e, fpname, lineno, line)
-        self._join_multiline_values()
-        # if any parsing errors occurred, raise an exception
-        if e:
-            raise e
-
-    def _join_multiline_values(self):
-        defaults = self.default_section, self._defaults
-        all_sections = itertools.chain((defaults,),
-                                       self._sections.items())
-        for section, options in all_sections:
-            for name, val in options.items():
-                if isinstance(val, list):
-                    val = '\n'.join(val).rstrip()
-                options[name] = self._interpolation.before_read(self,
-                                                                section,
-                                                                name, val)
-
-    def _read_defaults(self, defaults):
-        """Read the defaults passed in the initializer.
-        Note: values can be non-string."""
-        for key, value in defaults.items():
-            self._defaults[self.optionxform(key)] = value
-
-    def _handle_error(self, exc, fpname, lineno, line):
-        if not exc:
-            exc = ParsingError(fpname)
-        exc.append(lineno, repr(line))
-        return exc
-
-    def _unify_values(self, section, vars):
-        """Create a sequence of lookups with 'vars' taking priority over
-        the 'section' which takes priority over the DEFAULTSECT.
-
-        """
-        sectiondict = {}
-        try:
-            sectiondict = self._sections[section]
-        except KeyError:
-            if section != self.default_section:
-                raise NoSectionError(section) from None
-        # Update with the entry specific variables
-        vardict = {}
-        if vars:
-            for key, value in vars.items():
-                if value is not None:
-                    value = str(value)
-                vardict[self.optionxform(key)] = value
-        return _ChainMap(vardict, sectiondict, self._defaults)
-
-    def _convert_to_boolean(self, value):
-        """Return a boolean value translating from other types if necessary.
-        """
-        if value.lower() not in self.BOOLEAN_STATES:
-            raise ValueError('Not a boolean: %s' % value)
-        return self.BOOLEAN_STATES[value.lower()]
-
-    def _validate_value_types(self, *, section="", option="", value=""):
-        """Raises a TypeError for non-string values.
-
-        The only legal non-string value if we allow valueless
-        options is None, so we need to check if the value is a
-        string if:
-        - we do not allow valueless options, or
-        - we allow valueless options but the value is not None
-
-        For compatibility reasons this method is not used in classic set()
-        for RawConfigParsers. It is invoked in every case for mapping protocol
-        access and in ConfigParser.set().
-        """
-        if not isinstance(section, str):
-            raise TypeError("section names must be strings")
-        if not isinstance(option, str):
-            raise TypeError("option keys must be strings")
-        if not self._allow_no_value or value:
-            if not isinstance(value, str):
-                raise TypeError("option values must be strings")
-
-    @property
-    def converters(self):
-        return self._converters
-
-
-class ConfigParser(RawConfigParser):
-    """ConfigParser implementing interpolation."""
-
-    _DEFAULT_INTERPOLATION = BasicInterpolation()
-
-    def set(self, section, option, value=None):
-        """Set an option.  Extends RawConfigParser.set by validating type and
-        interpolation syntax on the value."""
-        self._validate_value_types(option=option, value=value)
-        super().set(section, option, value)
-
-    def add_section(self, section):
-        """Create a new section in the configuration.  Extends
-        RawConfigParser.add_section by validating if the section name is
-        a string."""
-        self._validate_value_types(section=section)
-        super().add_section(section)
-
-    def _read_defaults(self, defaults):
-        """Reads the defaults passed in the initializer, implicitly converting
-        values to strings like the rest of the API.
-
-        Does not perform interpolation for backwards compatibility.
-        """
-        try:
-            hold_interpolation = self._interpolation
-            self._interpolation = Interpolation()
-            self.read_dict({self.default_section: defaults})
-        finally:
-            self._interpolation = hold_interpolation
-
-
-class SafeConfigParser(ConfigParser):
-    """ConfigParser alias for backwards compatibility purposes."""
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        warnings.warn(
-            "The SafeConfigParser class has been renamed to ConfigParser "
-            "in Python 3.2. This alias will be removed in future versions."
-            " Use ConfigParser directly instead.",
-            DeprecationWarning, stacklevel=2
-        )
-
-
-class SectionProxy(MutableMapping):
-    """A proxy for a single section from a parser."""
-
-    def __init__(self, parser, name):
-        """Creates a view on a section of the specified `name` in `parser`."""
-        self._parser = parser
-        self._name = name
-        for conv in parser.converters:
-            key = 'get' + conv
-            getter = functools.partial(self.get, _impl=getattr(parser, key))
-            setattr(self, key, getter)
-
-    def __repr__(self):
-        return '<Section: {}>'.format(self._name)
-
-    def __getitem__(self, key):
-        if not self._parser.has_option(self._name, key):
-            raise KeyError(key)
-        return self._parser.get(self._name, key)
-
-    def __setitem__(self, key, value):
-        self._parser._validate_value_types(option=key, value=value)
-        return self._parser.set(self._name, key, value)
-
-    def __delitem__(self, key):
-        if not (self._parser.has_option(self._name, key) and
-                self._parser.remove_option(self._name, key)):
-            raise KeyError(key)
-
-    def __contains__(self, key):
-        return self._parser.has_option(self._name, key)
-
-    def __len__(self):
-        return len(self._options())
-
-    def __iter__(self):
-        return self._options().__iter__()
-
-    def _options(self):
-        if self._name != self._parser.default_section:
-            return self._parser.options(self._name)
-        else:
-            return self._parser.defaults()
-
-    @property
-    def parser(self):
-        # The parser object of the proxy is read-only.
-        return self._parser
-
-    @property
-    def name(self):
-        # The name of the section on a proxy is read-only.
-        return self._name
-
-    def get(self, option, fallback=None, *, raw=False, vars=None,
-            _impl=None, **kwargs):
-        """Get an option value.
-
-        Unless `fallback` is provided, `None` will be returned if the option
-        is not found.
-
-        """
-        # If `_impl` is provided, it should be a getter method on the parser
-        # object that provides the desired type conversion.
-        if not _impl:
-            _impl = self._parser.get
-        return _impl(self._name, option, raw=raw, vars=vars,
-                     fallback=fallback, **kwargs)
-
-
-class ConverterMapping(MutableMapping):
-    """Enables reuse of get*() methods between the parser and section proxies.
-
-    If a parser class implements a getter directly, the value for the given
-    key will be ``None``. The presence of the converter name here enables
-    section proxies to find and use the implementation on the parser class.
-    """
-
-    GETTERCRE = re.compile(r"^get(?P<name>.+)$")
-
-    def __init__(self, parser):
-        self._parser = parser
-        self._data = {}
-        for getter in dir(self._parser):
-            m = self.GETTERCRE.match(getter)
-            if not m or not callable(getattr(self._parser, getter)):
-                continue
-            self._data[m.group('name')] = None   # See class docstring.
-
-    def __getitem__(self, key):
-        return self._data[key]
-
-    def __setitem__(self, key, value):
-        try:
-            k = 'get' + key
-        except TypeError:
-            raise ValueError('Incompatible key: {} (type: {})'
-                             ''.format(key, type(key)))
-        if k == 'get':
-            raise ValueError('Incompatible key: cannot use "" as a name')
-        self._data[key] = value
-        func = functools.partial(self._parser._get_conv, conv=value)
-        func.converter = value
-        setattr(self._parser, k, func)
-        for proxy in self._parser.values():
-            getter = functools.partial(proxy.get, _impl=func)
-            setattr(proxy, k, getter)
-
-    def __delitem__(self, key):
-        try:
-            k = 'get' + (key or None)
-        except TypeError:
-            raise KeyError(key)
-        del self._data[key]
-        for inst in itertools.chain((self._parser,), self._parser.values()):
-            try:
-                delattr(inst, k)
-            except AttributeError:
-                # don't raise since the entry was present in _data, silently
-                # clean up
-                continue
-
-    def __iter__(self):
-        return iter(self._data)
-
-    def __len__(self):
-        return len(self._data)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/dol/trans.html b/docs/_modules/dol/trans.html deleted file mode 100644 index f6d0e2d..0000000 --- a/docs/_modules/dol/trans.html +++ /dev/null @@ -1,2758 +0,0 @@ - - - - - - - - dol.trans — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for dol.trans

-"""Transformation/wrapping tools"""
-from functools import wraps, partial, reduce
-import types
-from inspect import signature, Parameter
-from typing import Union, Iterable, Optional, Collection, Callable
-from warnings import warn
-from collections.abc import Iterable, KeysView, ValuesView, ItemsView
-
-from dol.errors import SetattrNotAllowed
-from dol.base import Store, KvReader, AttrNames
-from dol.util import lazyprop, num_of_args, attrs_of, wraps
-from dol.signatures import Sig, KO
-
-
-########################################################################################################################
-# Internal Utils
-
-
-def double_up_as_factory(decorator_func):
-    """Repurpose a decorator both as it's original form, and as a decorator factory.
-    That is, from a decorator that is defined do ``wrapped_func = decorator(func, **params)``,
-    make it also be able to do ``wrapped_func = decorator(**params)(func)``.
-
-    Note: You'll only be able to do this if all but the first argument are keyword-only,
-    and the first argument (the function to decorate) has a default of ``None`` (this is for your own good).
-    This is validated before making the "double up as factory" decorator.
-
-    >>> @double_up_as_factory
-    ... def decorator(func=None, *, multiplier=2):
-    ...     def _func(x):
-    ...         return func(x) * multiplier
-    ...     return _func
-    ...
-    >>> def foo(x):
-    ...     return x + 1
-    ...
-    >>> foo(2)
-    3
-    >>> wrapped_foo = decorator(foo, multiplier=10)
-    >>> wrapped_foo(2)
-    30
-    >>>
-    >>> multiply_by_3 = decorator(multiplier=3)
-    >>> wrapped_foo = multiply_by_3(foo)
-    >>> wrapped_foo(2)
-    9
-    >>>
-    >>> @decorator(multiplier=3)
-    ... def foo(x):
-    ...     return x + 1
-    ...
-    >>> foo(2)
-    9
-
-    Note that to be able to use double_up_as_factory, your first argument (the object to be wrapped) needs to default
-    to None and be the only argument that is not keyword-only (i.e. all other arguments need to be keyword only).
-
-    >>> @double_up_as_factory
-    ... def decorator_2(func, *, multiplier=2):
-    ...     '''Should not be able to be transformed with double_up_as_factory'''
-    Traceback (most recent call last):
-      ...
-    AssertionError: First argument of the decorator function needs to default to None. Was <class 'inspect._empty'>
-    >>> @double_up_as_factory
-    ... def decorator_3(func=None, multiplier=2):
-    ...     '''Should not be able to be transformed with double_up_as_factory'''
-    Traceback (most recent call last):
-      ...
-    AssertionError: All arguments (besides the first) need to be keyword-only
-
-    """
-
-    def validate_decorator_func(decorator_func):
-        first_param, *other_params = signature(
-            decorator_func
-        ).parameters.values()
-        assert (
-            first_param.default is None
-        ), f'First argument of the decorator function needs to default to None. Was {first_param.default}'
-        assert all(
-            p.kind == p.KEYWORD_ONLY for p in other_params
-        ), f'All arguments (besides the first) need to be keyword-only'
-        return True
-
-    validate_decorator_func(decorator_func)
-
-    @wraps(decorator_func)
-    def _double_up_as_factory(wrapped=None, **kwargs):
-        if wrapped is None:  # then we want a factory
-            return partial(decorator_func, **kwargs)
-        else:
-            return decorator_func(wrapped, **kwargs)
-
-    return _double_up_as_factory
-
-
-def _all_but_first_arg_are_keyword_only(func):
-    """
-    >>> def foo(a, *, b, c=2): ...
-    >>> _all_but_first_arg_are_keyword_only(foo)
-    True
-    >>> def bar(a, b, *, c=2): ...
-    >>> _all_but_first_arg_are_keyword_only(bar)
-    False
-    """
-    kinds = (p.kind for p in signature(func).parameters.values())
-    _ = next(
-        kinds
-    )  # consume first item, and all remaining should be KEYWORD_ONLY
-    return all(kind == Parameter.KEYWORD_ONLY for kind in kinds)
-
-
-# TODO: Separate the wrapper_assignments injection (and possibly make these not show up at the interface?)
-# FIXME: doctest line numbers not shown correctly when wrapped by store_decorator!
-def store_decorator(func):
-    """Helper to make store decorators.
-
-    You provide a class-decorating function ``func`` that takes a store type (and possibly additional params)
-    and returns another decorated store type.
-
-    ``store_decorator`` takes that ``func`` and provides an enhanced class decorator specialized for stores.
-    Namely it will:
-    - Add ``__module__``, ``__qualname__``, ``__name__`` and ``__doc__`` arguments to it
-    - Copy the aforementioned arguments to the decorated class, or copy the attributes of the original if not specified.
-    - Output a decorator that can be used in four different ways: a class/instance decorator/factory.
-
-    By class/instance decorator/factory we mean that if ``A`` is a class, ``a`` an instance of it,
-    and ``deco`` a decorator obtained with ``store_decorator(func)``,
-    we can use ``deco`` to
-    - class decorator: decorate a class
-    - class decorator factory: make a function that decorates classes
-    - instance decorator: decorate an instance of a store
-    - instancce decorator factor: make a function that decorates instances of stores
-
-    For example, say we have the following ``deco`` that we made with ``store_decorator``:
-
-    >>> @store_decorator
-    ... def deco(cls=None, *, x=1):
-    ...     # do stuff to cls, or a copy of it...
-    ...     cls.x = x  # like this for example
-    ...     return cls
-
-    And a class that has nothing to it:
-
-    >>> class A: ...
-
-    Nammely, it doesn't have an ``x``
-
-    >>> hasattr(A, 'x')
-    False
-
-    We make a ``decorated_A`` with ``deco`` (class decorator example)
-
-    >>> t = deco(A, x=42)
-    >>> assert isinstance(t, type)
-
-    and we see that we now have an ``x`` and it's 42
-
-    >>> hasattr(A, 'x')
-    True
-    >>> A.x
-    42
-
-    But we could have also made a factory to decorate ``A`` and anything else that comes our way.
-
-    >>> paint_it_42 = deco(x=42)
-    >>> decorated_A = paint_it_42(A)
-    >>> assert decorated_A.x == 42
-    >>> class B:
-    ...     x = 'destined to disappear'
-    >>> assert paint_it_42(B).x == 42
-
-    To be fair though, you'll probably see the factory usage appear in the following form,
-    where the class is decorated at definition time.
-
-    >>> @deco(x=42)
-    ... class B:
-    ...     pass
-    >>> assert B.x == 42
-
-    If your exists already, and you want to keep it as is (with the same name), you can
-    use subclassing to transform a copy of ``A`` instead, as below.
-    Also note in the following example, that ``deco`` was used without parentheses,
-    which is equivalent to ``@deco()``,
-    and yes, store_decorator makes that possible to, as long as your params have defaults
-
-    >>> @deco
-    ... class decorated_A(A):
-    ...     pass
-    >>> assert decorated_A.x == 1
-    >>> assert A.x == 42
-
-    Finally, you can also decorate instances:
-
-    >>> class A: ...
-    >>> a = A()
-    >>> hasattr(a, 'x')
-    False
-    >>> b = deco(a); assert b.x == 1; # b has an x and it's 1
-    >>> b = deco()(a); assert b.x == 1; # b has an x and it's 1
-    >>> b = deco(a, x=42); assert b.x == 42  # b has an x and it's 42
-    >>> b = deco(x=42)(a); assert b.x == 42; # b has an x and it's 42
-
-    WARNING: Note though that the type of ``b`` is not the same type as ``a``
-    >>> isinstance(b, a.__class__)
-    False
-
-    No, ``b`` is an instance of a ``dol.base.Store``, which is a class containing an
-    instance of a store (here, ``a``).
-
-    >>> type(b)
-    <class 'abc.StoreWrap'>
-    >>> b.store == a
-    True
-
-    Now, here's some more example, slightly closer to real usage
-
-    >>> from dol.trans import store_decorator
-    >>> from inspect import signature
-    >>>
-    >>> def rm_deletion(store=None, *, msg='Deletions not allowed.'):
-    ...     name = getattr(store, '__name__', 'Something') + '_w_sommething'
-    ...     assert isinstance(store, type), f"Should be a type, was {type(store)}: {store}"
-    ...     wrapped_store = type(name, (store,), {})
-    ...     wrapped_store.__delitem__ = lambda self, k: msg
-    ...     return wrapped_store
-    ...
-    >>> remove_deletion = store_decorator(rm_deletion)
-
-    See how the signature of the wrapper has some extra inputs that were injected (__module__, __qualname__, etc.):
-
-    >>> print(str(signature(remove_deletion)))
-    (store=None, *, msg='Deletions not allowed.', __module__=None, __name__=None, __qualname__=None, __doc__=None, __annotations__=None, __defaults__=None, __kwdefaults__=None)
-
-    Using it as a class decorator factory (the most common way):
-
-    As a class decorator "factory", without parameters (and without ()):
-
-    >>> from collections import UserDict
-    >>> @remove_deletion
-    ... class WD(UserDict):
-    ...     "Here's the doc"
-    ...     pass
-    >>> wd = WD(x=5, y=7)
-    >>> assert wd == UserDict(x=5, y=7)  # same as far as dict comparison goes
-    >>> assert wd.__delitem__('x') == 'Deletions not allowed.'
-    >>> assert wd.__doc__ == "Here's the doc"
-
-    As a class decorator "factory", with parameters:
-
-    >>> @remove_deletion(msg='No way. I do not trust you!!')
-    ... class WD(UserDict): ...
-    >>> wd = WD(x=5, y=7)
-    >>> assert wd == UserDict(x=5, y=7)  # same as far as dict comparison goes
-    >>> assert wd.__delitem__('x') == 'No way. I do not trust you!!'
-
-    The __doc__ is empty:
-
-    >>> assert WD.__doc__ == None
-
-    But we could specify a doc if we wanted to:
-
-    >>> @remove_deletion(__doc__="Hi, I'm a doc.")
-    ... class WD(UserDict):
-    ...     "This is the original doc, that will be overritten"
-    >>> assert WD.__doc__ == "Hi, I'm a doc."
-
-
-    The class decorations above are equivalent to the two following:
-
-    >>> WD = remove_deletion(UserDict)
-    >>> wd = WD(x=5, y=7)
-    >>> assert wd == UserDict(x=5, y=7)  # same as far as dict comparison goes
-    >>> assert wd.__delitem__('x') == 'Deletions not allowed.'
-    >>>
-    >>> WD = remove_deletion(UserDict, msg='No way. I do not trust you!!')
-    >>> wd = WD(x=5, y=7)
-    >>> assert wd == UserDict(x=5, y=7)  # same as far as dict comparison goes
-    >>> assert wd.__delitem__('x') == 'No way. I do not trust you!!'
-
-    But we can also decorate instances. In this case they will be wrapped in a Store class
-    before being passed on to the actual decorator.
-
-    >>> d = UserDict(x=5, y=7)
-    >>> wd = remove_deletion(d)
-    >>> assert wd == d  # same as far as dict comparison goes
-    >>> assert wd.__delitem__('x') == 'Deletions not allowed.'
-    >>>
-    >>> d = UserDict(x=5, y=7)
-    >>> wd = remove_deletion(d, msg='No way. I do not trust you!!')
-    >>> assert wd == d  # same as far as dict comparison goes
-    >>> assert wd.__delitem__('x') == 'No way. I do not trust you!!'
-
-    """
-
-    # wrapper_assignments = ('__module__', '__qualname__', '__name__', '__doc__', '__annotations__')
-    wrapper_assignments = (
-        '__module__',
-        '__name__',
-        '__qualname__',
-        '__doc__',
-        '__annotations__',
-        '__defaults__',
-        '__kwdefaults__',
-    )
-
-    @wraps(func)
-    def _func_wrapping_store_in_cls_if_not_type(store, **kwargs):
-
-        specials = dict()
-        for a in wrapper_assignments:
-            v = kwargs.pop(a, getattr(store, a, None))
-            if v is not None:
-                specials[a] = v
-
-        if not isinstance(store, type):
-            store_instance = store
-            StoreWrap = type(
-                'StoreWrap', (Store,), {}
-            )  # a copy of Store, so Store isn't transformed directly
-            WrapperStore = func(StoreWrap, **kwargs)
-            r = WrapperStore(store_instance)
-        else:
-            assert _all_but_first_arg_are_keyword_only(func), (
-                "To use decorating_store_cls, all but the first of your function's arguments need to be all keyword only. "
-                f'The signature was {func.__qualname__}{signature(func)}'
-            )
-            r = func(store, **kwargs)
-
-        for k, v in specials.items():
-            if v is not None:
-                setattr(r, k, v)
-
-        return r
-
-    _func_wrapping_store_in_cls_if_not_type.func = (
-        func  # TODO: look for usages, and if not, use __wrapped__
-    )
-
-    # @wraps(func)
-    wrapper_sig = Sig(func).merge_with_sig(
-        [dict(name=a, default=None, kind=KO) for a in wrapper_assignments],
-        ch_to_all_pk=False,
-    )
-
-    # TODO: Re-use double_up_as_factory here
-    @wrapper_sig
-    def wrapper(store=None, **kwargs):
-        if store is None:  # then we want a factory
-            return partial(_func_wrapping_store_in_cls_if_not_type, **kwargs)
-        else:
-            wrapped_store_cls = _func_wrapping_store_in_cls_if_not_type(
-                store, **kwargs
-            )
-
-            return wrapped_store_cls
-
-    # Make sure the wrapper (yes, also the wrapper) has the same key dunders as the func
-    for a in wrapper_assignments:
-        v = getattr(func, a, None)
-        if v is not None:
-            setattr(wrapper, a, v)
-
-    return wrapper
-
-
-def ensure_set(x):
-    if isinstance(x, str):
-        x = [x]
-    return set(x)
-
-
-def get_class_name(cls, dflt_name=None):
-    name = getattr(cls, '__qualname__', None)
-    if name is None:
-        name = getattr(getattr(cls, '__class__', object), '__qualname__', None)
-        if name is None:
-            if dflt_name is not None:
-                return dflt_name
-            else:
-                raise ValueError(f'{cls} has no name I could extract')
-    return name
-
-
-def store_wrap(obj):
-    if isinstance(obj, type):
-
-        @wraps(type(obj), updated=())  # added this: test
-        class StoreWrap(Store):
-            @wraps(obj.__init__)
-            def __init__(self, *args, **kwargs):
-                persister = obj(*args, **kwargs)
-                super().__init__(persister)
-
-        return StoreWrap
-    else:
-        return Store(obj)
-
-
-# # Older version, kept around for awhile, for review:
-# def store_wrap(obj, name=None):
-#     if isinstance(obj, type):
-#         name = name or f"{get_class_name(obj, 'StoreWrap')}Store"
-#
-#         class StoreWrap(Store):
-#             @wraps(obj.__init__)
-#             def __init__(self, *args, **kwargs):
-#                 persister = obj(*args, **kwargs)
-#                 super().__init__(persister)
-#
-#         StoreWrap.__qualname__ = name
-#         # if hasattr(obj, '_cls_trans'):
-#         #     StoreWrap._cls_trans = obj._cls_trans
-#         return StoreWrap
-#     else:
-#         return Store(obj)
-
-
-def _is_bound(method):
-    return hasattr(method, '__self__')
-
-
-def _first_param_is_an_instance_param(params):
-    return len(params) > 0 and list(params)[0] in self_names
-
-
-# TODO: Add validation of func: That all but perhaps 1 argument (not counting self) has a default
-def _has_unbound_self(func):
-    """
-
-    Args:
-        func:
-
-    Returns:
-
-    >>> def f1(x): ...
-    >>> assert _has_unbound_self(f1) == 0
-    >>>
-    >>> def f2(self, x): ...
-    >>> assert _has_unbound_self(f2) == 1
-    >>>
-    >>> f3 = lambda self, x: True
-    >>> assert _has_unbound_self(f3) == 1
-    >>>
-    >>> class A:
-    ...     def bar(self, x): ...
-    ...     def foo(dacc, x): ...
-    >>> a = A()
-    >>>
-    >>> _has_unbound_self(a.bar)
-    0
-    >>> _has_unbound_self(a.foo)
-    0
-    >>> _has_unbound_self(A.bar)
-    1
-    >>> _has_unbound_self(A.foo)
-    0
-    >>>
-    """
-    try:
-        params = signature(func).parameters
-    except ValueError:
-        # If there was a problem getting the signature, assume it's a signature-less builtin (so not a bound method)
-        return False
-    if len(params) == 0:
-        # no argument, so we can't be wrapping anything!!!
-        raise ValueError(
-            "The function has no parameters, so I can't guess which one you want to wrap"
-        )
-    elif not _is_bound(func) and _first_param_is_an_instance_param(params):
-        return True
-    else:
-        return False
-
-
-def transparent_key_method(self, k):
-    return k
-
-
-def mk_kv_reader_from_kv_collection(
-    kv_collection, name=None, getitem=transparent_key_method
-):
-    """Make a KvReader class from a Collection class.
-
-    Args:
-        kv_collection: The Collection class
-        name: The name to give the KvReader class (by default, it will be kv_collection.__qualname__ + 'Reader')
-        getitem: The method that will be assigned to __getitem__. Should have the (self, k) signature.
-            By default, getitem will be transparent_key_method, returning the key as is.
-            This default is useful when you want to delegate the actual getting to a _obj_of_data wrapper.
-
-    Returns: A KvReader class that subclasses the input kv_collection
-    """
-
-    name = name or kv_collection.__qualname__ + 'Reader'
-    reader_cls = type(
-        name, (kv_collection, KvReader), {'__getitem__': getitem}
-    )
-    return reader_cls
-
-
-def raise_disabled_error(functionality):
-    def disabled_function(*args, **kwargs):
-        raise ValueError(f'{functionality} is disabled')
-
-    return disabled_function
-
-
-def disable_delitem(o):
-    if hasattr(o, '__delitem__'):
-        o.__delitem__ = raise_disabled_error('deletion')
-    return o
-
-
-def disable_setitem(o):
-    if hasattr(o, '__setitem__'):
-        o.__setitem__ = raise_disabled_error('writing')
-    return o
-
-
-def mk_read_only(o):
-    return disable_delitem(disable_setitem(o))
-
-
-def is_iterable(x):
-    return isinstance(x, Iterable)
-
-
-def add_ipython_key_completions(store):
-    """Add tab completion that shows you the keys of the store.
-    Note: ipython already adds local path listing automatically,
-     so you'll still get those along with your valid store keys.
-    """
-
-    def _ipython_key_completions_(self):
-        return self.keys()
-
-    if isinstance(store, type):
-        store._ipython_key_completions_ = _ipython_key_completions_
-    else:
-        setattr(
-            store,
-            '_ipython_key_completions_',
-            types.MethodType(_ipython_key_completions_, store),
-        )
-    return store
-
-
-from dol.util import copy_attrs
-from dol.errors import OverWritesNotAllowedError
-
-
-def disallow_overwrites(store, *, error_msg=None, disable_deletes=True):
-    assert isinstance(store, type), 'store needs to be a type'
-    if hasattr(store, '__setitem__'):
-
-        def __setitem__(self, k, v):
-            if k in self:
-                raise OverWritesNotAllowedError(
-                    'key {} already exists and cannot be overwritten. '
-                    'If you really want to write to that key, delete it before writing'.format(
-                        k
-                    )
-                )
-            return super().__setitem__(k, v)
-
-
-class OverWritesNotAllowedMixin:
-    """Mixin for only allowing a write to a key if they key doesn't already exist.
-    Note: Should be before the persister in the MRO.
-
-    >>> class TestPersister(OverWritesNotAllowedMixin, dict):
-    ...     pass
-    >>> p = TestPersister()
-    >>> p['foo'] = 'bar'
-    >>> #p['foo'] = 'bar2'  # will raise error
-    >>> p['foo'] = 'this value should not be stored' # doctest: +NORMALIZE_WHITESPACE
-    Traceback (most recent call last):
-      ...
-    dol.errors.OverWritesNotAllowedError: key foo already exists and cannot be overwritten.
-        If you really want to write to that key, delete it before writing
-    >>> p['foo']  # foo is still bar
-    'bar'
-    >>> del p['foo']
-    >>> p['foo'] = 'this value WILL be stored'
-    >>> p['foo']
-    'this value WILL be stored'
-    """
-
-    @staticmethod
-    def wrap(cls):
-        # TODO: Consider moving to trans and making instances wrappable too
-        class NoOverWritesClass(OverWritesNotAllowedMixin, cls):
-            ...
-
-        copy_attrs(
-            NoOverWritesClass, cls, ('__name__', '__qualname__', '__module__')
-        )
-        return NoOverWritesClass
-
-    def __setitem__(self, k, v):
-        if self.__contains__(k):
-            raise OverWritesNotAllowedError(
-                'key {} already exists and cannot be overwritten. '
-                'If you really want to write to that key, delete it before writing'.format(
-                    k
-                )
-            )
-        return super().__setitem__(k, v)
-
-
-########################################################################################################################
-# Caching keys
-
-# TODO: If a read-one-by-one (vs the current read all implementation) is necessary one day,
-#   see https://github.com/zahlman/indexify/blob/master/src/indexify.py for ideas
-#   but probably buffered (read by chunks) version of the later is better.
-@store_decorator
-def cached_keys(
-    store=None,
-    *,
-    keys_cache: Union[callable, Collection] = list,
-    iter_to_container=None,  # deprecated: use keys_cache instead
-    cache_update_method='update',
-    name: str = None,  # TODO: might be able to be deprecated since included in store_decorator
-    __module__=None,  # TODO: might be able to be deprecated since included in store_decorator
-) -> Union[callable, KvReader]:
-    """Make a class that wraps input class's __iter__ becomes cached.
-
-    Quite often we have a lot of keys, that we get from a remote data source, and don't want to have to ask for
-    them again and again, having them be fetched, sent over the network, etc.
-    So we need caching.
-
-    But this caching is not the typical read caching, since it's __iter__ we want to cache, and that's a generator.
-    So we'll implement a store class decorator specialized for this.
-
-    The following decorator, when applied to a class (that has an __iter__), will perform the __iter__ code, consuming
-    all items of the generator and storing them in _keys_cache, and then will yield from there every subsequent call.
-
-    It is assumed, if you're using the cached_keys transformation, that you're dealing with static data
-    (or data that can be considered static for the life of the store -- for example, when conducting analytics).
-    If you ever need to refresh the cache during the life of the store, you can to delete _keys_cache like this:
-    ```
-    del your_store._keys_cache
-    ```
-    Once you do that, the next time you try to ask something about the contents of the store, it will actually do
-    a live query again, as for the first time.
-
-    Note: The default keys_cache is list though in many cases, you'd probably should use set, or an explicitly
-    computer set instead. The reason list is used as the default is because (1) we didn't want to assume that
-    order did not matter (maybe it does to you) and (2) we didn't want to assume that your keys were hashable.
-    That said, if you're keys are hashable, and order does not matter, use set. That'll give you two things:
-    (a) your `key in store` checks will be faster (O(1) instead of O(n)) and (b) you'll enforce unicity of keys.
-
-    Know also that if you precompute the keys you want to cache with a container that has an update
-    method (by default `update`) your cache updates will be faster and if the container you use has
-    a `remove` method, you'll be able to delete as well.
-
-    Args:
-        store: The store instance or class to wrap (must have an __iter__), or None if you want a decorator.
-        keys_cache: An explicit collection of keys
-        iter_to_container: The function that will be applied to existing __iter__() and assigned to cache.
-            The default is list. Another useful one is the sorted function.
-        cache_update_method: Name of the keys_cache update method to use, if it is an attribute of keys_cache.
-            Note that this cache_update_method will be used only
-                if keys_cache is an explicit iterable and has that attribute
-                if keys_cache is a callable and has that attribute.
-            The default None
-        name: The name of the new class
-
-    Returns:
-        If store is:
-            None: Will return a decorator that can be applied to a store
-            a store class: Will return a wrapped class that caches it's keys
-            a store instance: Will return a wrapped instance that caches it's keys
-
-        The instances of such key-cached classes have some extra attributes:
-            _explicit_keys: The actual cache. An iterable container
-            update_keys_cache: Is called if a user uses the instance to mutate the store (i.e. write or delete).
-
-    You have two ways of caching keys:
-    - By providing the explicit list of keys you want cache (and use)
-    - By providing a callable that will iterate through your store and collect an explicit list of keys
-
-    Let's take a simple dict as our original store.
-    >>> source = dict(c=3, b=2, a=1)
-
-    Specify an iterable, and it will be used as the cached keys
-    >>> cached = cached_keys(source, keys_cache='bc')
-    >>> list(cached.items())  # notice that the order you get things is also ruled by the cache
-    [('b', 2), ('c', 3)]
-
-    Specify a callable, and it will apply it to the existing keys to make your cache
-    >>> list(cached_keys(source, keys_cache=sorted))
-    ['a', 'b', 'c']
-
-    You can use the callable keys_cache specification to filter as well!
-    Oh, and let's demo the fact that if you don't specify the store, it will make a store decorator for you:
-    >>> cache_my_keys = cached_keys(keys_cache=lambda keys: list(filter(lambda k: k >= 'b', keys)))
-    >>> d = cache_my_keys(source)  # used as to transform an instance
-    >>> list(d)
-    ['c', 'b']
-
-    Let's use that same `cache_my_keys` to decorate a class instead:
-    >>> cached_dict = cache_my_keys(dict)
-    >>> d = cached_dict(c=3, b=2, a=1)
-    >>> list(d)
-    ['c', 'b']
-
-    Note that there's still an underlying store (dict) that has the data:
-    >>> repr(d)  # repr isn't wrapped, so you can still see your underlying dict
-    "{'c': 3, 'b': 2, 'a': 1}"
-
-    And yes, you can still add elements,
-    >>> d['z'] = 26
-    >>> list(d.items())
-    [('c', 3), ('b', 2), ('z', 26)]
-
-    do bulk updates,
-    >>> d.update({'more': 'of this'}, more_of='that')
-    >>> list(d.items())
-    [('c', 3), ('b', 2), ('z', 26), ('more', 'of this'), ('more_of', 'that')]
-
-    and delete...
-    >>> del d['more']
-    >>> list(d.items())
-    [('c', 3), ('b', 2), ('z', 26), ('more_of', 'that')]
-
-    But careful! Know what you're doing if you try to get creative. Have a look at this:
-    >>> d['a'] = 100  # add an 'a' item
-    >>> d.update(and_more='of that')  # update to add yet another item
-    >>> list(d.items())
-    [('c', 3), ('b', 2), ('z', 26), ('more_of', 'that')]
-
-    Indeed: No 'a' or 'and_more'.
-
-    Now... they were indeed added. Or to be more precise, the value of the already existing a was changed,
-    and a new ('and_more', 'of that') item was indeed added in the underlying store:
-    >>> repr(d)
-    "{'c': 3, 'b': 2, 'a': 100, 'z': 26, 'more_of': 'that', 'and_more': 'of that'}"
-
-    But you're not seeing it.
-
-    Why?
-
-    Because you chose to use a callable keys_cache that doesn't have an 'update' method.
-    When your _keys_cache attribute (the iterable cache) is not updatable itself, the
-    way updates work is that we iterate through the underlying store (where the updates actually took place),
-    and apply the keys_cache (callable) to that iterable.
-
-    So what happened here was that you have your new 'a' and 'and_more' items, but your cached version of the
-    store doesn't see it because it's filtered out. On the other hand, check out what happens if you have
-    an updateable cache.
-
-    Using `set` instead of `list`, after the `filter`.
-
-    >>> cache_my_keys = cached_keys(keys_cache=set)
-    >>> d = cache_my_keys(source)  # used as to transform an instance
-    >>> sorted(d)  # using sorted because a set's order is not always the same
-    ['a', 'b', 'c']
-    >>> d['a'] = 100
-    >>> d.update(and_more='of that')  # update to add yet another item
-    >>> sorted(d.items())
-    [('a', 100), ('and_more', 'of that'), ('b', 2), ('c', 3)]
-
-    This example was to illustrate a more subtle aspect of cached_keys. You would probably deal with
-    the filter concern in a different way in this case. But the rope is there -- it's your choice on how
-    to use it.
-
-    And here's some more examples if that wasn't enough!
-
-    >>> # Lets cache the keys of a dict.
-    >>> cached_dict = cached_keys(dict)
-    >>> d = cached_dict(a=1, b=2, c=3)
-    >>> # And you get a store that behaves as expected (but more speed and RAM)
-    >>> list(d)
-    ['a', 'b', 'c']
-    >>> list(d.items())  # whether you iterate with .keys(), .values(), or .items()
-    [('a', 1), ('b', 2), ('c', 3)]
-
-    This is where the keys are stored:
-    >>> d._keys_cache
-    ['a', 'b', 'c']
-
-    >>> # Let's demo the iter_to_container argument. The default is "list", which will just consume the iter in order
-    >>> sorted_dict = cached_keys(dict, keys_cache=list)
-    >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1})
-    >>> list(s)  # keys will be in the order they were defined
-    ['b', 'a', 'c']
-    >>> sorted_dict = cached_keys(dict, keys_cache=sorted)
-    >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1})
-    >>> list(s)  # keys will be sorted
-    ['a', 'b', 'c']
-    >>> sorted_dict = cached_keys(dict, keys_cache=lambda x: sorted(x, key=len))
-    >>> s = sorted_dict({'bbb': 3, 'aa': 2, 'c': 1})
-    >>> list(s)  # keys will be sorted according to their length
-    ['c', 'aa', 'bbb']
-
-    If you change the keys (adding new ones with __setitem__ or update, or removing with pop or popitem)
-    then the cache is recomputed (the first time you use an operation that iterates over keys)
-    >>> d.update(d=4)  # let's add an element (try d['d'] = 4 as well)
-    >>> list(d)
-    ['a', 'b', 'c', 'd']
-    >>> d['e'] = 5
-    >>> list(d.items())  # whether you iterate with .keys(), .values(), or .items()
-    [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
-
-    >>> @cached_keys
-    ... class A:
-    ...     def __iter__(self):
-    ...         yield from [1, 2, 3]
-    >>> # Note, could have also used this form: AA = cached_keys(A)
-    >>> a = A()
-    >>> list(a)
-    [1, 2, 3]
-    >>> a._keys_cache = ['a', 'b', 'c']  # changing the cache, to prove that subsequent listing will read from there
-    >>> list(a)  # proof:
-    ['a', 'b', 'c']
-    >>>
-
-    >>> # Let's demo the iter_to_container argument. The default is "list", which will just consume the iter in order
-    >>> sorted_dict = cached_keys(dict, keys_cache=list)
-    >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1})
-    >>> list(s)  # keys will be in the order they were defined
-    ['b', 'a', 'c']
-    >>> sorted_dict = cached_keys(dict, keys_cache=sorted)
-    >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1})
-    >>> list(s)  # keys will be sorted
-    ['a', 'b', 'c']
-    >>> sorted_dict = cached_keys(dict, keys_cache=lambda x: sorted(x, key=len))
-    >>> s = sorted_dict({'bbb': 3, 'aa': 2, 'c': 1})
-    >>> list(s)  # keys will be sorted according to their length
-    ['c', 'aa', 'bbb']
-    """
-    if iter_to_container is not None:
-        assert callable(iter_to_container)
-        warn(
-            "The argument name 'iter_to_container' is being deprecated in favor of the more general 'keys_cache'"
-        )
-        # assert keys_cache == iter_to_container
-
-    assert isinstance(
-        store, type
-    ), f'store_cls must be a type, was a {type(store)}: {store}'
-
-    # name = name or 'IterCached' + get_class_name(store_cls)
-    name = name or get_class_name(store)
-    __module__ = __module__ or getattr(store, '__module__', None)
-
-    class cached_cls(store):
-        _keys_cache = None
-
-    cached_cls.__name__ = name
-
-    # cached_cls = type(name, (store_cls,), {"_keys_cache": None})
-
-    # The following class is not the class that will be returned, but the class from which we'll take the methods
-    #   that will be copied in the class that will be returned.
-    @_define_keys_values_and_items_according_to_iter
-    class CachedIterMethods:
-        _explicit_keys = False
-        _updatable_cache = False
-        _iter_to_container = None
-        if hasattr(keys_cache, cache_update_method):
-            _updatable_cache = True
-        if is_iterable(
-            keys_cache
-        ):  # if keys_cache is iterable, it is the cache instance itself.
-            _keys_cache = keys_cache
-            _explicit_keys = True
-        elif callable(keys_cache):
-            # if keys_cache is not iterable, but callable, we'll use it to make the keys_cache from __iter__
-            _iter_to_container = keys_cache
-
-            @lazyprop
-            def _keys_cache(self):
-                # print(iter_to_container)
-                return keys_cache(
-                    super(cached_cls, self).__iter__()
-                )  # TODO: Should it be iter(super(...)?
-
-        # if not callable(_explicit_keys):
-
-        # If keys_cache_update is None (the default), the method 'update' will be searched for as above,
-        #   and if not found, will fall back to None.
-        # if isinstance(keys_cache_update, str):
-        #     if (_explicit_keys and hasattr(_explicit_keys, '__class__')
-        #             and hasattr(_explicit_keys.__class__, keys_cache_update)):
-        #         keys_cache_update = getattr(_explicit_keys.__class__, keys_cache_update)
-
-        # if (_explicit_keys and hasattr(_explicit_keys, '__class__')
-        #         and hasattr(_explicit_keys.__class__, 'update')):
-        #     keys_cache_update = _explicit_keys.__class__.update
-        #
-
-        @property
-        def _iter_cache(self):  # for back-compatibility
-            warn(
-                'The new name for `_iter_cache` is `_keys_cache`. Start using that!',
-                DeprecationWarning,
-            )
-            return self._keys_cache
-
-        def __iter__(self):
-            # if getattr(self, '_keys_cache', None) is None:
-            #     self._keys_cache = iter_to_container(super(cached_cls, self).__iter__())
-            yield from self._keys_cache
-
-        def __len__(self):
-            return len(self._keys_cache)
-
-        def items(self):
-            for k in self._keys_cache:
-                yield k, self[k]
-
-        def __contains__(self, k):
-            return k in self._keys_cache
-
-        # The write and update stuff ###################################################################
-
-        if _updatable_cache:
-
-            def update_keys_cache(self, keys):
-                """updates the keys by calling the
-                """
-                update_func = getattr(self._keys_cache, cache_update_method)
-                update_func(self._keys_cache, keys)
-
-            update_keys_cache.__doc__ = (
-                'Updates the _keys_cache by calling its {} method'
-            )
-        else:
-
-            def update_keys_cache(self, keys):
-                """Updates the _keys_cache by deleting the attribute
-                """
-                try:
-                    del self._keys_cache
-                    # print('deleted _keys_cache')
-                except AttributeError:
-                    pass
-
-        def __setitem__(self, k, v):
-            super(cached_cls, self).__setitem__(k, v)
-            # self.store[k] = v
-            if (
-                k not in self
-            ):  # just to avoid deleting the cache if we already had the key
-                self.update_keys_cache((k,))
-                # Note: different construction performances: (k,)->10ns, [k]->38ns, {k}->50ns
-
-        def update(self, other=(), **kwds):
-            # print(other, kwds)
-            # super(cached_cls, self).update(other, **kwds)
-            super_setitem = super(cached_cls, self).__setitem__
-            for k in other:
-                # print(k, other[k])
-                super_setitem(k, other[k])
-                # self.store[k] = other[k]
-            self.update_keys_cache(other)
-
-            for k, v in kwds.items():
-                # print(k, v)
-                super_setitem(k, v)
-                # self.store[k] = v
-            self.update_keys_cache(kwds)
-
-        def __delitem__(self, k):
-            self._keys_cache.remove(k)
-            super(cached_cls, self).__delitem__(k)
-
-    # And this is where we add all the needed methods (for example, no __setitem__ won't be added if the original
-    #   class didn't have one in the first place.
-    special_attrs = {
-        'update_keys_cache',
-        '_keys_cache',
-        '_explicit_keys',
-        '_updatable_cache',
-    }
-    for attr in special_attrs | (
-        AttrNames.KvPersister
-        & attrs_of(cached_cls)
-        & attrs_of(CachedIterMethods)
-    ):
-        setattr(cached_cls, attr, getattr(CachedIterMethods, attr))
-
-    if __module__ is not None:
-        cached_cls.__module__ = __module__
-
-    if hasattr(store, '__doc__'):
-        cached_cls.__doc__ = store.__doc__
-
-    return cached_cls
-
-
-cache_iter = cached_keys  # TODO: Alias, partial it and make it more like the original, for back compatibility.
-
-
-@store_decorator
-def catch_and_cache_error_keys(
-    store=None,
-    *,
-    errors_caught=Exception,
-    error_callback=None,
-    use_cached_keys_after_completed_iter=True,
-):
-    """Store that will cache keys as they're accessed, separating those that raised errors and those that didn't.
-    Getting a key will still through an error, but the access attempts will be collected in an ._error_keys attribute.
-    Successfful attemps will be stored in _keys_cache.
-    Retrieval iteration (items() or values()) will on the other hand, skip the error (while still caching it).
-    If the iteration completes (and use_cached_keys_after_completed_iter), the use_cached_keys flag is turned on,
-    which will result in the store now getting it's keys from the _keys_cache.
-
-    >>> @catch_and_cache_error_keys(
-    ...     error_callback=lambda store, key, err: print(f"Error with {key} key: {err}"))
-    ... class Blacklist(dict):
-    ...     _black_list = {'black', 'list'}
-    ...
-    ...     def __getitem__(self, k):
-    ...         if k not in self._black_list:
-    ...             return super().__getitem__(k)
-    ...         else:
-    ...             raise KeyError(f"Nope, that's from the black list!")
-    >>>
-    >>> s = Blacklist(black=7,  friday=20, frenzy=13)
-    >>> list(s)
-    ['black', 'friday', 'frenzy']
-    >>> list(s.items())
-    Error with black key: "Nope, that's from the black list!"
-    [('friday', 20), ('frenzy', 13)]
-    >>> sorted(s)  # sorting to get consistent output
-    ['frenzy', 'friday']
-
-
-    See that? First we had three keys, then we iterated and got only 2 items (fortunately,
-    we specified an ``error_callback`` so we ccould see that the iteration actually dropped a key).
-    That's strange. And even stranger is the fact that when we list our keys again, we get only two.
-
-    You don't like it? Neither do I. But
-    - It's not a completely outrageous behavior -- if you're talking to live data, it
-        often happens that you get more, or less, from one second to another.
-    - This store isn't meant to be long living, but rather meant to solve the problem of skiping
-        items that are problematic (for example, malformatted files), with a trace of
-        what was skipped and what's valid (in case we need to iterate again and don't want to
-        bear the hit of requesting values for keys we already know are problematic.
-
-    Here's a little peep of what is happening under the hood.
-    Meet ``_keys_cache`` and ``_error_keys`` sets (yes, unordered -- so know it) that are meant
-    to acccumulate valid and problematic keys respectively.
-
-    >>> s = Blacklist(black=7,  friday=20, frenzy=13)
-    >>> list(s)
-    ['black', 'friday', 'frenzy']
-    >>> s._keys_cache, s._error_keys
-    (set(), set())
-    >>> s['friday']
-    20
-    >>> s._keys_cache, s._error_keys
-    ({'friday'}, set())
-    >>> s['black']
-    Traceback (most recent call last):
-      ...
-    KeyError: "Nope, that's from the black list!"
-    >>> s._keys_cache, s._error_keys
-    ({'friday'}, {'black'})
-
-    But see that we still have the full list:
-
-    >>> list(s)
-    ['black', 'friday', 'frenzy']
-
-    Meet ``use_cached_keys``: He's the culprit. It's a flag that indicates whether we should be
-    using the cached keys or not. Obviously, it'll start off being ``False``:
-
-    >>> s.use_cached_keys
-    False
-
-    Now we could set it to ``True`` manually to change the mode.
-    But know that this switch happens automatically (UNLESS you specify otherwise by saying:
-    ``use_cached_keys_after_completed_iter=False``) when ever you got through a
-    VALUE-PRODUCING iteration (i.e. entirely consuming `items()` or `values()`).
-
-    >>> sorted(s.values())  # sorting to get consistent output
-    Error with black key: "Nope, that's from the black list!"
-    [13, 20]
-
-    """
-
-    assert isinstance(
-        store, type
-    ), f'store_cls must be a type, was a {type(store)}: {store}'
-
-    # assert isinstance(store, Mapping), f"store_cls must be a Mapping. Was not. mro is {store.mro()}: {store}"
-
-    # class cached_cls(store):
-    #     _keys_cache = None
-    #     _error_keys = None
-
-    # The following class is not the class that will be returned, but the class from which we'll take the methods
-    #   that will be copied in the class that will be returned.
-    # @_define_keys_values_and_items_according_to_iter
-    class CachedKeyErrorsStore(store):
-        @wraps(store.__init__)
-        def __init__(self, *args, **kwargs):
-            super().__init__(*args, **kwargs)
-            self._error_keys = set()
-            self._keys_cache = set()
-            self.use_cached_keys = False
-            self.use_cached_keys_after_completed_iter = (
-                use_cached_keys_after_completed_iter
-            )
-            self.errors_caught = errors_caught
-            self.error_callback = error_callback
-
-        def __getitem__(self, k):
-            if self.use_cached_keys:
-                return super().__getitem__(k)
-            else:
-                try:
-                    v = super().__getitem__(k)
-                    self._keys_cache.add(k)
-                    return v
-                except self.errors_caught:
-                    self._error_keys.add(k)
-                    raise
-
-        def __iter__(self):
-            # if getattr(self, '_keys_cache', None) is None:
-            #     self._keys_cache = iter_to_container(super(cached_cls, self).__iter__())
-            if self.use_cached_keys:
-                yield from self._keys_cache
-            else:
-                yield from super().__iter__()
-
-        def __len__(self):
-            if self.use_cached_keys:
-                return len(self._keys_cache)
-            else:
-                return super().__len__()
-
-        def items(self):
-            if self.use_cached_keys:
-                for k in self._keys_cache:
-                    yield k, self[k]
-            else:
-                for k in self:
-                    try:
-                        yield k, self[k]
-                    except self.errors_caught as err:
-                        if self.error_callback is not None:
-                            self.error_callback(store, k, err)
-            if self.use_cached_keys_after_completed_iter:
-                self.use_cached_keys = True
-
-        def values(self):
-            if self.use_cached_keys:
-                yield from (self[k] for k in self._keys_cache)
-            else:
-                yield from (v for k, v in self.items())
-
-        def __contains__(self, k):
-            if self.use_cached_keys:
-                return k in self._keys_cache
-            else:
-                return super().__contains__(k)
-
-    return CachedKeyErrorsStore
-
-
-def iterate_values_and_accumulate_non_error_keys(
-    store, cache_keys_here: list, errors_caught=Exception, error_callback=None
-):
-    for k in store:
-        try:
-            v = store[k]
-            cache_keys_here.append(k)
-            yield v
-        except errors_caught as err:
-            if error_callback is not None:
-                error_callback(store, k, err)
-
-
-########################################################################################################################
-# Filtering iteration
-
-
-def take_everything(key):
-    return True
-
-
-# TODO: Factor out the method injection pattern (e.g. __getitem__, __setitem__ and __delitem__ are nearly identical)
-@store_decorator
-def filt_iter(
-    store=None,
-    *,
-    filt: Union[callable, Iterable] = take_everything,
-    name=None,
-    __module__=None,  # TODO: might be able to be deprecated since included in store_decorator
-):
-    """Make a wrapper that will transform a store (class or instance thereof) into a sub-store (i.e. subset of keys).
-
-    Args:
-        filt: A callable or iterable:
-            callable: Boolean filter function. A func taking a key and and returns True iff the key should be included.
-            iterable: The collection of keys you want to filter "in"
-        name: The name to give the wrapped class
-
-    Returns: A wrapper (that then needs to be applied to a store instance or class.
-
-    >>> filtered_dict = filt_iter(filt=lambda k: (len(k) % 2) == 1)(dict)  # keep only odd length keys
-    >>>
-    >>> s = filtered_dict({'a': 1, 'bb': object, 'ccc': 'a string', 'dddd': [1, 2]})
-    >>>
-    >>> list(s)
-    ['a', 'ccc']
-    >>> 'a' in s  # True because odd (length) key
-    True
-    >>> 'bb' in s  # False because odd (length) key
-    False
-    >>> assert s.get('bb', None) == None
-    >>> len(s)
-    2
-    >>> list(s.keys())
-    ['a', 'ccc']
-    >>> list(s.values())
-    [1, 'a string']
-    >>> list(s.items())
-    [('a', 1), ('ccc', 'a string')]
-    >>> s.get('a')
-    1
-    >>> assert s.get('bb') is None
-    >>> s['x'] = 10
-    >>> list(s.items())
-    [('a', 1), ('ccc', 'a string'), ('x', 10)]
-    >>> try:
-    ...     s['xx'] = 'not an odd key'
-    ...     raise ValueError("This should have failed")
-    ... except KeyError:
-    ...     pass
-    """
-
-    # if store is None:
-    if not callable(filt):  # if filt is not a callable...
-        # ... assume it's the collection of keys you want and make a filter function to filter those "in".
-        assert next(iter(filt)), 'filt should be a callable, or an iterable'
-        keys_that_should_be_filtered_in = set(filt)
-
-        def filt(k):
-            return k in keys_that_should_be_filtered_in
-
-    __module__ = __module__ or getattr(store, '__module__', None)
-
-    name = name or 'Filtered' + get_class_name(store)
-    wrapped_cls = type(name, (store,), {})
-
-    def __iter__(self):
-        yield from filter(filt, super(wrapped_cls, self).__iter__())
-
-    wrapped_cls.__iter__ = __iter__
-
-    _define_keys_values_and_items_according_to_iter(wrapped_cls)
-
-    def __len__(self):
-        c = 0
-        for _ in self.__iter__():
-            c += 1
-        return c
-
-    wrapped_cls.__len__ = __len__
-
-    def __contains__(self, k):
-        if filt(k):
-            return super(wrapped_cls, self).__contains__(k)
-        else:
-            return False
-
-    wrapped_cls.__contains__ = __contains__
-
-    if hasattr(wrapped_cls, '__getitem__'):
-
-        def __getitem__(self, k):
-            if filt(k):
-                return super(wrapped_cls, self).__getitem__(k)
-            else:
-                raise KeyError(f'Key not in store: {k}')
-
-        wrapped_cls.__getitem__ = __getitem__
-
-    if hasattr(wrapped_cls, 'get'):
-
-        def get(self, k, default=None):
-            if filt(k):
-                return super(wrapped_cls, self).get(k, default)
-            else:
-                return default
-
-        wrapped_cls.get = get
-
-    if hasattr(wrapped_cls, '__setitem__'):
-
-        def __setitem__(self, k, v):
-            if filt(k):
-                return super(wrapped_cls, self).__setitem__(k, v)
-            else:
-                raise KeyError(f'Key not in store: {k}')
-
-        wrapped_cls.__setitem__ = __setitem__
-
-    if hasattr(wrapped_cls, '__delitem__'):
-
-        def __delitem__(self, k):
-            if filt(k):
-                return super(wrapped_cls, self).__delitem__(k)
-            else:
-                raise KeyError(f'Key not in store: {k}')
-
-        wrapped_cls.__delitem__ = __delitem__
-
-    # if __module__ is not None:
-    #     wrapped_cls.__module__ = __module__
-    #
-    # if hasattr(collection_cls, '__doc__'):
-    #     wrapped_cls.__doc__ = store.__doc__
-
-    return wrapped_cls
-
-
-########################################################################################################################
-# Wrapping keys and values
-
-self_names = frozenset(['self', 'store'])
-
-
-def _define_keys_values_and_items_according_to_iter(cls):
-    if hasattr(cls, 'keys'):
-
-        def keys(self):
-            # yield from self.__iter__()  # TODO: Should it be iter(self)?
-            return KeysView(self)
-
-        cls.keys = keys
-
-    if hasattr(cls, 'values'):
-
-        def values(self):
-            # yield from (self[k] for k in self)
-            return ValuesView(self)
-
-        cls.values = values
-
-    if hasattr(cls, 'items'):
-
-        def items(self):
-            # yield from ((k, self[k]) for k in self)
-            return ItemsView(self)
-
-        cls.items = items
-
-    return cls
-
-
-# TODO: would like to keep dict_keys methods (like __sub__, isdisjoint). How do I do so?
-class _DefineKeysValuesAndItemsAccordingToIter:
-    def keys(self):
-        yield from self.__iter__()  # TODO: Should it be iter(self)?
-
-    def values(self):
-        yield from (self[k] for k in self)
-
-    def items(self):
-        yield from ((k, self[k]) for k in self)
-
-
-# TODO: Consider deprecation. Besides the name arg (whose usefulness is doubtful), this is just Store.wrap
-def kv_wrap_persister_cls(persister_cls, name=None):
-    """Make a class that wraps a persister into a dol.base.Store,
-
-    Args:
-        persister_cls: The persister class to wrap
-
-    Returns: A Store wrapping the persister (see dol.base)
-
-    >>> A = kv_wrap_persister_cls(dict)
-    >>> a = A()
-    >>> a['one'] = 1
-    >>> a['two'] = 2
-    >>> a['three'] = 3
-    >>> list(a.items())
-    [('one', 1), ('two', 2), ('three', 3)]
-    >>> assert hasattr(a, '_obj_of_data')  # for example, it has this magic method
-    >>> # If you overwrite the _obj_of_data method, you'll transform outcomming values with it.
-    >>> # For example, say the data you stored were minutes, but you want to get then in secs...
-    >>> a._obj_of_data = lambda data: data * 60
-    >>> list(a.items())
-    [('one', 60), ('two', 120), ('three', 180)]
-    >>>
-    >>> # And if you want to have class that has this weird "store minutes, retrieve seconds", you can do this:
-    >>> class B(kv_wrap_persister_cls(dict)):
-    ...     def _obj_of_data(self, data):
-    ...         return data * 60
-    >>> b = B()
-    >>> b.update({'one': 1, 'two': 2, 'three': 3})  # you can write several key-value pairs at once this way!
-    >>> list(b.items())
-    [('one', 60), ('two', 120), ('three', 180)]
-    >>> # Warning! Advanced under-the-hood chat coming up.... Note this:
-    >>> print(b)
-    {'one': 1, 'two': 2, 'three': 3}
-    >>> # What?!? Well, remember, printing an object calls the objects __str__, which usually calls __repr__
-    >>> # The wrapper doesn't wrap those methods, since they don't have consistent behaviors.
-    >>> # Here you're getting the __repr__ of the underlying dict store, without the key and value transforms.
-    >>>
-    >>> # Say you wanted to transform the incoming minute-unit data, converting to secs BEFORE they were stored...
-    >>> class C(kv_wrap_persister_cls(dict)):
-    ...     def _data_of_obj(self, obj):
-    ...         return obj * 60
-    >>> c = C()
-    >>> c.update(one=1, two=2, three=3)  # yet another way you can write multiple key-vals at once
-    >>> list(c.items())
-    [('one', 60), ('two', 120), ('three', 180)]
-    >>> print(c)  # but notice that unlike when we printed b, here the stored data is actually transformed!
-    {'one': 60, 'two': 120, 'three': 180}
-    >>>
-    >>> # Now, just to demonstrate key transformation, let's say that we need internal (stored) keys to be upper case,
-    >>> # but external (the keys you see when listed) ones to be lower case, for some reason...
-    >>> class D(kv_wrap_persister_cls(dict)):
-    ...     _data_of_obj = staticmethod(lambda obj: obj * 60)  # to demonstrated another way of doing this
-    ...     _key_of_id = lambda self, _id: _id.lower()  # note if you don't specify staticmethod, 1st arg must be self
-    ...     def _id_of_key(self, k):  # a function definition like you're used to
-    ...         return k.upper()
-    >>> d = D()
-    >>> d['oNe'] = 1
-    >>> d.update(TwO=2, tHrEE=3)
-    >>> list(d.items())  # you see clean lower cased keys at the interface of the store
-    [('one', 60), ('two', 120), ('three', 180)]
-    >>> # but internally, the keys are all upper case
-    >>> print(d)  # equivalent to print(d.store), so keys and values not wrapped (values were transformed before stored)
-    {'ONE': 60, 'TWO': 120, 'THREE': 180}
-    >>>
-    >>> # On the other hand, careful, if you gave the data directly to D, you wouldn't get that.
-    >>> d = D({'one': 1, 'two': 2, 'three': 3})
-    >>> print(d)
-    {'one': 1, 'two': 2, 'three': 3}
-    >>> # Thus is because when you construct a D with the dict, it initializes the dicts data with it directly
-    >>> # before the key/val transformers are in place to do their jobs.
-    """
-
-    cls = Store.wrap(persister_cls)
-
-    # TODO: The whole name and qualname thing -- is it really necessary, correct, what we want?
-    name = name or (persister_cls.__name__ + 'PWrapped')
-    qname = name or (persister_cls.__qualname__ + 'PWrapped')
-
-    cls.__qualname__ = qname
-    cls.__name__ = name
-
-    return cls
-
-    # name = name or (persister_cls.__qualname__ + "PWrapped")
-    #
-    # cls = type(name, (Store,), {})
-    #
-    # # TODO: Investigate sanity and alternatives (cls = type(name, (Store, persister_cls), {}) leads to MRO problems)
-    # for attr in set(dir(persister_cls)) - set(dir(Store)):
-    #     persister_cls_attribute = getattr(persister_cls, attr)
-    #     setattr(cls, attr, persister_cls_attribute)  # copy the attribute over to cls
-    #
-    # if hasattr(persister_cls, '__doc__'):
-    #     cls.__doc__ = persister_cls.__doc__
-    #
-    # @wraps(persister_cls.__init__)
-    # def __init__(self, *args, **kwargs):
-    #     super(cls, self).__init__(persister_cls(*args, **kwargs))
-    #
-    # cls.__init__ = __init__
-    #
-    # return cls
-
-
-def _wrap_outcoming(
-    store_cls: type, wrapped_method: str, trans_func: Optional[callable] = None
-):
-    """Output-transforming wrapping of the wrapped_method of store_cls.
-    The transformation is given by trans_func, which could be a one (trans_func(x)
-    or two (trans_func(self, x)) argument function.
-
-    Args:
-        store_cls: The class that will be transformed
-        wrapped_method: The method (name) that will be transformed.
-        trans_func: The transformation function.
-        wrap_arg_idx: The index of the
-
-    Returns: Nothing. It transforms the class in-place
-
-    >>> from dol.trans import store_wrap
-    >>> S = store_wrap(dict)
-    >>> _wrap_outcoming(S, '_key_of_id', lambda x: f'wrapped_{x}')
-    >>> s = S({'a': 1, 'b': 2})
-    >>> list(s)
-    ['wrapped_a', 'wrapped_b']
-    >>> _wrap_outcoming(S, '_key_of_id', lambda self, x: f'wrapped_{x}')
-    >>> s = S({'a': 1, 'b': 2}); assert list(s) == ['wrapped_a', 'wrapped_b']
-    >>> class A:
-    ...     def __init__(self, prefix='wrapped_'):
-    ...         self.prefix = prefix
-    ...     def _key_of_id(self, x):
-    ...         return self.prefix + x
-    >>> _wrap_outcoming(S, '_key_of_id', A(prefix='wrapped_')._key_of_id)
-    >>> s = S({'a': 1, 'b': 2}); assert list(s) == ['wrapped_a', 'wrapped_b']
-    >>>
-    >>> S = store_wrap(dict)
-    >>> _wrap_outcoming(S, '_obj_of_data', lambda x: x * 7)
-    >>> s = S({'a': 1, 'b': 2})
-    >>> list(s.values())
-    [7, 14]
-    """
-    if trans_func is not None:
-        wrapped_func = getattr(store_cls, wrapped_method)
-
-        if not _has_unbound_self(trans_func):
-            # print(f"00000: {store_cls}: {wrapped_method}, {trans_func}, {wrapped_func}, {wrap_arg_idx}")
-            @wraps(wrapped_func)
-            def new_method(self, x):
-                # # Long form (for explanation)
-                # super_method = getattr(super(store_cls, self), wrapped_method)
-                # output_of_super_method = super_method(x)
-                # transformed_output_of_super_method = trans_func(output_of_super_method)
-                # return transformed_output_of_super_method
-                return trans_func(
-                    getattr(super(store_cls, self), wrapped_method)(x)
-                )
-
-        else:
-            # print(f"11111: {store_cls}: {wrapped_method}, {trans_func}, {wrapped_func}, {wrap_arg_idx}")
-            @wraps(wrapped_func)
-            def new_method(self, x):
-                # # Long form (for explanation)
-                # super_method = getattr(super(store_cls, self), wrapped_method)
-                # output_of_super_method = super_method(x)
-                # transformed_output_of_super_method = trans_func(self, output_of_super_method)
-                # return transformed_output_of_super_method
-                return trans_func(
-                    self, getattr(super(store_cls, self), wrapped_method)(x)
-                )
-
-        setattr(store_cls, wrapped_method, new_method)
-
-
-def _wrap_ingoing(
-    store_cls, wrapped_method: str, trans_func: Optional[callable] = None
-):
-    if trans_func is not None:
-        wrapped_func = getattr(store_cls, wrapped_method)
-
-        if not _has_unbound_self(trans_func):
-
-            @wraps(wrapped_func)
-            def new_method(self, x):
-                return getattr(super(store_cls, self), wrapped_method)(
-                    trans_func(x)
-                )
-
-        else:
-
-            @wraps(wrapped_func)
-            def new_method(self, x):
-                return getattr(super(store_cls, self), wrapped_method)(
-                    trans_func(self, x)
-                )
-
-        setattr(store_cls, wrapped_method, new_method)
-
-
-@store_decorator
-def wrap_kvs(
-    store=None,
-    *,
-    name=None,
-    key_of_id=None,
-    id_of_key=None,
-    obj_of_data=None,
-    data_of_obj=None,
-    preset=None,
-    postget=None,
-    __module__=None,
-    outcoming_key_methods=(),
-    outcoming_value_methods=(),
-    ingoing_key_methods=(),
-    ingoing_value_methods=(),
-):
-    r"""Make a Store that is wrapped with the given key/val transformers.
-
-    Naming convention:
-        Morphemes:
-            key: outer key
-            _id: inner key
-            obj: outer value
-            data: inner value
-        Grammar:
-            Y_of_X: means that you get a Y output when giving an X input. Also known as X_to_Y.
-
-
-    Args:
-        store: Store class or instance
-        name: Name to give the wrapper class
-        key_of_id: The outcoming key transformation function.
-            Forms are `k = key_of_id(_id)` or `k = key_of_id(self, _id)`
-        id_of_key: The ingoing key transformation function.
-            Forms are `_id = id_of_key(k)` or `_id = id_of_key(self, k)`
-        obj_of_data: The outcoming val transformation function.
-            Forms are `obj = obj_of_data(data)` or `obj = obj_of_data(self, data)`
-        data_of_obj: The ingoing val transformation function.
-            Forms are `data = data_of_obj(obj)` or `data = data_of_obj(self, obj)`
-        preset: A function that is called before doing a `__setitem__`.
-            The function is called with both `k` and `v` as inputs, and should output a transformed value.
-            The intent use is to do ingoing value transformations conditioned on the key.
-            For example, you may want to serialize an object depending on if you're writing to a
-             '.csv', or '.json', or '.pickle' file.
-            Forms are `preset(k, obj)` or `preset(self, k, obj)`
-        postget: A function that is called after the value `v` for a key `k` is be `__getitem__`.
-            The function is called with both `k` and `v` as inputs, and should output a transformed value.
-            The intent use is to do outcoming value transformations conditioned on the key.
-            We already have `obj_of_data` for outcoming value trans, but cannot condition it's behavior on k.
-            For example, you may want to deserialize the bytes of a '.csv', or '.json', or '.pickle' in different ways.
-            Forms are `obj = postget(k, data)` or `obj = postget(self, k, data)`
-
-    Returns:
-
-    >>> def key_of_id(_id):
-    ...     return _id.upper()
-    >>> def id_of_key(k):
-    ...     return k.lower()
-    >>> def obj_of_data(data):
-    ...     return data - 100
-    >>> def data_of_obj(obj):
-    ...     return obj + 100
-    >>>
-    >>> A = wrap_kvs(dict, name='A',
-    ...             key_of_id=key_of_id, id_of_key=id_of_key, obj_of_data=obj_of_data, data_of_obj=data_of_obj)
-    >>> a = A()
-    >>> a['KEY'] = 1
-    >>> a  # repr is just the base class (dict) repr, so shows "inside" the store (lower case keys and +100)
-    {'key': 101}
-    >>> a['key'] = 2
-    >>> print(a)  # repr is just the base class (dict) repr, so shows "inside" the store (lower case keys and +100)
-    {'key': 102}
-    >>> a['kEy'] = 3
-    >>> a  # repr is just the base class (dict) repr, so shows "inside" the store (lower case keys and +100)
-    {'key': 103}
-    >>> list(a)  # but from the point of view of the interface the keys are all upper case
-    ['KEY']
-    >>> list(a.items())  # and the values are those we put there.
-    [('KEY', 3)]
-    >>>
-    >>> # And now this: Showing how to condition the value transform (like obj_of_data), but conditioned on key.
-    >>> B = wrap_kvs(dict, name='B', postget=lambda k, v: f'upper {v}' if k[0].isupper() else f'lower {v}')
-    >>> b = B()
-    >>> b['BIG'] = 'letters'
-    >>> b['small'] = 'text'
-    >>> list(b.items())
-    [('BIG', 'upper letters'), ('small', 'lower text')]
-    >>>
-    >>>
-    >>> # Let's try preset and postget. We'll wrap a dict and write the same list of lists object to
-    >>> # keys ending with .csv, .json, and .pkl, specifying the obvious extension-dependent
-    >>> # serialization/deserialization we want to associate with it.
-    >>>
-    >>> # First, some very simple csv transformation functions
-    >>> to_csv = lambda LoL: '\\n'.join(map(','.join, map(lambda L: (x for x in L), LoL)))
-    >>> from_csv = lambda csv: list(map(lambda x: x.split(','), csv.split('\\n')))
-    >>> LoL = [['a','b','c'],['d','e','f']]
-    >>> assert from_csv(to_csv(LoL)) == LoL
-    >>>
-    >>> import json, pickle
-    >>>
-    >>> def preset(k, v):
-    ...     if k.endswith('.csv'):
-    ...         return to_csv(v)
-    ...     elif k.endswith('.json'):
-    ...         return json.dumps(v)
-    ...     elif k.endswith('.pkl'):
-    ...         return pickle.dumps(v)
-    ...     else:
-    ...         return v  # as is
-    ...
-    ...
-    >>> def postget(k, v):
-    ...     if k.endswith('.csv'):
-    ...         return from_csv(v)
-    ...     elif k.endswith('.json'):
-    ...         return json.loads(v)
-    ...     elif k.endswith('.pkl'):
-    ...         return pickle.loads(v)
-    ...     else:
-    ...         return v  # as is
-    ...
-    >>> mydict = wrap_kvs(dict, preset=preset, postget=postget)
-    >>>
-    >>> obj = [['a','b','c'],['d','e','f']]
-    >>> d = mydict()
-    >>> d['foo.csv'] = obj  # store the object as csv
-    >>> d  # "printing" a dict by-passes the transformations, so we see the data in the "raw" format it is stored in.
-    {'foo.csv': 'a,b,c\\nd,e,f'}
-    >>> d['foo.csv']  # but if we actually ask for the data, it deserializes to our original object
-    [['a', 'b', 'c'], ['d', 'e', 'f']]
-    >>> d['bar.json'] = obj  # store the object as json
-    >>> d
-    {'foo.csv': 'a,b,c\\nd,e,f', 'bar.json': '[["a", "b", "c"], ["d", "e", "f"]]'}
-    >>> d['bar.json']
-    [['a', 'b', 'c'], ['d', 'e', 'f']]
-    >>> d['bar.json'] = {'a': 1, 'b': [1, 2], 'c': 'normal json'}  # let's write a normal json instead.
-    >>> d
-    {'foo.csv': 'a,b,c\\nd,e,f', 'bar.json': '{"a": 1, "b": [1, 2], "c": "normal json"}'}
-    >>> del d['foo.csv']
-    >>> del d['bar.json']
-    >>> d['foo.pkl'] = obj  # 'save' obj as pickle
-    >>> d['foo.pkl']
-    [['a', 'b', 'c'], ['d', 'e', 'f']]
-
-    # TODO: Add tests for outcoming_key_methods etc.
-    """
-    name = name or store.__qualname__ + 'Wrapped'
-
-    # TODO: This is not the best way to handle this. Investigate another way. ######################
-    global_names = set(globals()).union(locals())
-    if name in global_names:
-        raise NameError('That name is already in use')
-    # TODO: ########################################################################################
-
-    store_cls = kv_wrap_persister_cls(store, name=name)  # experiment
-    store_cls._cls_trans = None
-
-    # store_cls = type(name, (store,), {})
-    # store_cls._cls_trans = None
-
-    def cls_trans(store_cls: type):
-        for method_name in {'_key_of_id'} | ensure_set(outcoming_key_methods):
-            _wrap_outcoming(store_cls, method_name, key_of_id)
-
-        for method_name in {'_obj_of_data'} | ensure_set(
-            outcoming_value_methods
-        ):
-            _wrap_outcoming(store_cls, method_name, obj_of_data)
-
-        for method_name in {'_id_of_key'} | ensure_set(ingoing_key_methods):
-            _wrap_ingoing(store_cls, method_name, id_of_key)
-
-        for method_name in {'_data_of_obj'} | ensure_set(
-            ingoing_value_methods
-        ):
-            _wrap_ingoing(store_cls, method_name, data_of_obj)
-
-        # TODO: postget and preset uses num_of_args. Not robust:
-        #  Should only count args with no defaults or partial won't be able to be used to make postget/preset funcs
-        # TODO: Extract postget and preset patterns?
-        if postget is not None:
-            if num_of_args(postget) < 2:
-                raise ValueError(
-                    'A postget function needs to have (key, value) or (self, key, value) arguments'
-                )
-
-            if not _has_unbound_self(postget):
-
-                def __getitem__(self, k):
-                    return postget(k, super(store_cls, self).__getitem__(k))
-
-            else:
-
-                def __getitem__(self, k):
-                    return postget(
-                        self, k, super(store_cls, self).__getitem__(k)
-                    )
-
-            store_cls.__getitem__ = __getitem__
-
-        if preset is not None:
-            if num_of_args(preset) < 2:
-                raise ValueError(
-                    'A preset function needs to have (key, value) or (self, key, value) arguments'
-                )
-
-            if not _has_unbound_self(preset):
-
-                def __setitem__(self, k, v):
-                    return super(store_cls, self).__setitem__(k, preset(k, v))
-
-            else:
-
-                def __setitem__(self, k, v):
-                    return super(store_cls, self).__setitem__(
-                        k, preset(self, k, v)
-                    )
-
-            store_cls.__setitem__ = __setitem__
-
-        if __module__ is not None:
-            store_cls.__module__ = __module__
-
-        # add an attribute containing the cls_trans.
-        # This is is both for debugging and introspection use,
-        # as well as if we need to pass on the transformation in a recursive situation
-        store_cls._cls_trans = cls_trans
-
-        return store_cls
-
-    return cls_trans(store_cls)
-
-
-def _kv_wrap_outcoming_keys(trans_func):
-    """Transform 'out-coming' keys, that is, the keys you see when you ask for them,
-    say, through __iter__(), keys(), or first element of the items() pairs.
-
-    Use this when you wouldn't use the keys in their original format,
-    or when you want to extract information from it.
-
-    Warning: If you haven't also wrapped incoming keys with a corresponding inverse transformation,
-    you won't be able to use the outcoming keys to fetch data.
-
-    >>> from collections import UserDict
-    >>> S = kv_wrap.outcoming_keys(lambda x: x[5:])(UserDict)
-    >>> s = S({'root/foo': 10, 'root/bar': 'xo'})
-    >>> list(s)
-    ['foo', 'bar']
-    >>> list(s.keys())
-    ['foo', 'bar']
-
-    # TODO: Asymmetric key trans breaks getting items (therefore items()). Resolve (remove items() for asym keys?)
-    # >>> list(s.items())
-    # [('foo', 10), ('bar', 'xo')]
-    """
-
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_kr'
-        )
-        return wrap_kvs(o, name=name, key_of_id=trans_func)
-
-    return wrapper
-
-
-def _kv_wrap_ingoing_keys(trans_func):
-    """Transform 'in-going' keys, that is, the keys you see when you ask for them,
-    say, through __iter__(), keys(), or first element of the items() pairs.
-
-    Use this when your context holds objects themselves holding key information, but you don't want to
-    (because you shouldn't) 'manually' extract that information and construct the key manually every time you need
-    to write something or fetch some existing data.
-
-    Warning: If you haven't also wrapped outcoming keys with a corresponding inverse transformation,
-    you won't be able to use the incoming keys to fetch data.
-
-    >>> from collections import UserDict
-    >>> S = kv_wrap.ingoing_keys(lambda x: 'root/' + x)(UserDict)
-    >>> s = S()
-    >>> s['foo'] = 10
-    >>> s['bar'] = 'xo'
-    >>> list(s)
-    ['root/foo', 'root/bar']
-    >>> list(s.keys())
-    ['root/foo', 'root/bar']
-
-    # TODO: Asymmetric key trans breaks getting items (therefore items()). Resolve (remove items() for asym keys?)
-    # >>> list(s.items())
-    # [('root/foo', 10), ('root/bar', 'xo')]
-    """
-
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_kw'
-        )
-        return wrap_kvs(o, name=name, id_of_key=trans_func)
-
-    return wrapper
-
-
-def _kv_wrap_outcoming_vals(trans_func):
-    """Transform 'out-coming' values, that is, the values you see when you ask for them,
-    say, through the values() or the second element of items() pairs.
-    This can be seen as adding a de-serialization layer: trans_func being the de-serialization function.
-
-    For example, say your store gives you values of the bytes type, but you want to use text, or gives you text,
-    but you want it to be interpreted as a JSON formatted text and get a dict instead. Both of these are
-    de-serialization layers, or out-coming value transformations.
-
-    Warning: If it matters, make sure you also wrapped with a corresponding inverse serialization.
-
-    >>> from collections import UserDict
-    >>> S = kv_wrap.outcoming_vals(lambda x: x * 2)(UserDict)
-    >>> s = S(foo=10, bar='xo')
-    >>> list(s.values())
-    [20, 'xoxo']
-    >>> list(s.items())
-    [('foo', 20), ('bar', 'xoxo')]
-    """
-
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_vr'
-        )
-        return wrap_kvs(o, name=name, obj_of_data=trans_func)
-
-    return wrapper
-
-
-def _kv_wrap_ingoing_vals(trans_func):
-    """Transform 'in-going' values, that is, the values at the level of the store's interface are transformed
-    to a different value before writing to the wrapped store.
-    This can be seen as adding a serialization layer: trans_func being the serialization function.
-
-    For example, say you have a list of audio samples, and you want to save these in a WAV format.
-
-    Warning: If it matters, make sure you also wrapped with a corresponding inverse de-serialization.
-
-    >>> from collections import UserDict
-    >>> S = kv_wrap.ingoing_vals(lambda x: x * 2)(UserDict)
-    >>> s = S()
-    >>> s['foo'] = 10
-    >>> s['bar'] = 'xo'
-    >>> list(s.values())
-    [20, 'xoxo']
-    >>> list(s.items())
-    [('foo', 20), ('bar', 'xoxo')]
-    """
-
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_vw'
-        )
-        return wrap_kvs(o, name=name, data_of_obj=trans_func)
-
-    return wrapper
-
-
-def _ingoing_vals_wrt_to_keys(trans_func):
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_vwk'
-        )
-        return wrap_kvs(o, name=name, preset=trans_func)
-
-    return wrapper
-
-
-def _outcoming_vals_wrt_to_keys(trans_func):
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_vrk'
-        )
-        return wrap_kvs(o, name=name, postget=trans_func)
-
-    return wrapper
-
-
-def mk_trans_obj(**kwargs):
-    """Convenience method to quickly make a trans_obj (just an object holding some trans functions"""
-    # TODO: Could make this more flexible (assuming here only staticmethods) and validate inputs...
-    return type(
-        'TransObj', (), {k: staticmethod(v) for k, v in kwargs.items()}
-    )()
-
-
-def kv_wrap(trans_obj):
-    """
-    kv_wrap: A function that makes a wrapper (a decorator) that will get the wrappers from methods of the input object.
-
-    kv_wrap also has attributes:
-        outcoming_keys, ingoing_keys, outcoming_vals, ingoing_vals, and val_reads_wrt_to_keys
-    which will only add a single specific wrapper (specified as a function), when that's what you need.
-
-    """
-
-    key_of_id = getattr(trans_obj, '_key_of_id', None)
-    id_of_key = getattr(trans_obj, '_id_of_key', None)
-    obj_of_data = getattr(trans_obj, '_obj_of_data', None)
-    data_of_obj = getattr(trans_obj, '_data_of_obj', None)
-    preset = getattr(trans_obj, '_preset', None)
-    postget = getattr(trans_obj, '_postget', None)
-
-    def wrapper(o, name=None):
-        name = (
-            name
-            or getattr(o, '__qualname__', getattr(o.__class__, '__qualname__'))
-            + '_kr'
-        )
-        return wrap_kvs(
-            o,
-            name=name,
-            key_of_id=key_of_id,
-            id_of_key=id_of_key,
-            obj_of_data=obj_of_data,
-            data_of_obj=data_of_obj,
-            preset=preset,
-            postget=postget,
-        )
-
-    return wrapper
-
-
-kv_wrap.mk_trans_obj = mk_trans_obj  # to have a trans_obj maker handy
-kv_wrap.outcoming_keys = _kv_wrap_outcoming_keys
-kv_wrap.ingoing_keys = _kv_wrap_ingoing_keys
-kv_wrap.outcoming_vals = _kv_wrap_outcoming_vals
-kv_wrap.ingoing_vals = _kv_wrap_ingoing_vals
-kv_wrap.ingoing_vals_wrt_to_keys = _ingoing_vals_wrt_to_keys
-kv_wrap.outcoming_vals_wrt_to_keys = _outcoming_vals_wrt_to_keys
-
-
-def mk_wrapper(wrap_cls):
-    """
-
-    You have a wrapper class and you want to make a wrapper out of it,
-    that is, a decorator factory with which you can make wrappers, like this:
-    ```
-    wrapper = mk_wrapper(wrap_cls)
-    ```
-    that you can then use to transform stores like thiis:
-    ```
-    MyStore = wrapper(**wrapper_kwargs)(StoreYouWantToTransform)
-    ```
-
-    :param wrap_cls:
-    :return:
-
-    >>> class RelPath:
-    ...     def __init__(self, root):
-    ...         self.root = root
-    ...         self._root_length = len(root)
-    ...     def _key_of_id(self, _id):
-    ...         return _id[self._root_length:]
-    ...     def _id_of_key(self, k):
-    ...         return self.root + k
-    >>> relpath_wrap = mk_wrapper(RelPath)
-    >>> RelDict = relpath_wrap(root='foo/')(dict)
-    >>> s = RelDict()
-    >>> s['bar'] = 42
-    >>> assert list(s) == ['bar']
-    >>> assert s['bar'] == 42
-    >>> assert str(s) == "{'foo/bar': 42}"  # reveals that actually, behind the scenes, there's a "foo/" prefix
-    """
-
-    @wraps(wrap_cls)
-    def wrapper(*args, **kwargs):
-        return kv_wrap(wrap_cls(*args, **kwargs))
-
-    return wrapper
-
-
-@double_up_as_factory
-def add_wrapper_method(wrap_cls=None, *, method_name='wrapper'):
-    """Decorator that adds a wrapper method (itself a decorator) to a wrapping class
-    Clear?
-    See `mk_wrapper` function and doctest example if not.
-
-    What `add_wrapper_method` does is just to add a `"wrapper"` method
-    (or another name if you ask for it) to `wrap_cls`, so that you can use that
-    class for it's purpose of transforming stores more conveniently.
-
-    :param wrap_cls: The wrapper class (the definitioin of the transformation.
-        If None, the functiion will make a decorator to decorate wrap_cls later
-    :param method_name: The method name you want to use (default is 'wrapper')
-
-    >>>
-    >>> @add_wrapper_method
-    ... class RelPath:
-    ...     def __init__(self, root):
-    ...         self.root = root
-    ...         self._root_length = len(root)
-    ...     def _key_of_id(self, _id):
-    ...         return _id[self._root_length:]
-    ...     def _id_of_key(self, k):
-    ...         return self.root + k
-    ...
-    >>> RelDict = RelPath.wrapper(root='foo/')(dict)
-    >>> s = RelDict()
-    >>> s['bar'] = 42
-    >>> assert list(s) == ['bar']
-    >>> assert s['bar'] == 42
-    >>> assert str(s) == "{'foo/bar': 42}"  # reveals that actually, behind the scenes, there's a "foo/" prefix
-    """
-    setattr(wrap_cls, method_name, mk_wrapper(wrap_cls))
-    return wrap_cls
-
-
-########################################################################################################################
-# Aliasing
-
-_method_name_for = {
-    'write': '__setitem__',
-    'read': '__getitem__',
-    'delete': '__delitem__',
-    'list': '__iter__',
-    'count': '__len__',
-}
-
-
-@store_decorator
-def add_path_get(store=None, *, name=None, path_type: type = tuple):
-    """
-    Make nested stores accessible through key paths.
-
-    Say you have some nested stores.
-    You know... like a `ZipFileReader` store whose values are `ZipReader`s,
-    whose values are bytes of the zipped files (and you can go on... whose (json) values are...).
-
-    Well, you can access any node of this nested tree of stores like this:
-
-    ```
-        MyStore[key_1][key_2][key_3]
-    ```
-
-    And that's fine. But maybe you'd like to do it this way instead:
-
-    ```
-        MyStore[key_1, key_2, key_3]
-    ```
-
-    Or like this:
-
-    ```
-        MyStore['key_1/key_2/key_3']
-    ```
-
-    Or this:
-
-    ```
-        MyStore['key_1.key_2.key_3']
-    ```
-
-    You get the point. This is what `add_path_get` is meant for.
-
-    Args:
-        store: The store (class or instance) you're wrapping.
-            If not specified, the function will return a decorator.
-        name: The name to give the class (not applicable to instance wrapping)
-        path_type: The type that paths are expressed as. Needs to be an Iterable type. By default, a tuple.
-            This is used to decide whether the key should be taken as a "normal" key of the store,
-            or should be used to iterate through, recursively getting values.
-
-    Returns: A wrapped store (class or instance), or a store wrapping decorator (if store is not specified)
-
-    See Also: `dol.paths.PathGetMixin`, `dol.paths.KeyPath`
-
-    >>> # wrapping an instance
-    >>> s = add_path_get({'a': {'b': {'c': 42}}})
-    >>> s['a']
-    {'b': {'c': 42}}
-    >>> s['a', 'b']
-    {'c': 42}
-    >>> s['a', 'b', 'c']
-    42
-    >>> # wrapping a class
-    >>> S = add_path_get(dict)
-    >>> s = S(a={'b': {'c': 42}})
-    >>> assert s['a'] == {'b': {'c': 42}}; assert s['a', 'b'] == {'c': 42}; assert s['a', 'b', 'c'] == 42
-    >>>
-    >>> # using add_path_get as a decorator
-    >>> @add_path_get
-    ... class S(dict):
-    ...    pass
-    >>> s = S(a={'b': {'c': 42}})
-    >>> assert s['a'] == {'b': {'c': 42}};
-    >>> assert s['a', 'b'] == s['a']['b'];
-    >>> assert s['a', 'b', 'c'] == s['a']['b']['c']
-    >>>
-    >>> # a different kind of path?
-    >>> # You can choose a different path_type, but sometimes (say both keys and key paths are strings)
-    >>> # you need to involve more tools. Like dol.paths.KeyPath...
-    >>> from dol.paths import KeyPath
-    >>> from dol.trans import kv_wrap
-    >>> SS = kv_wrap(KeyPath(path_sep='.'))(S)
-    >>> s = SS({'a': {'b': {'c': 42}}})
-    >>> assert s['a'] == {'b': {'c': 42}}; assert s['a.b'] == s['a']['b']; assert s['a.b.c'] == s['a']['b']['c']
-    """
-    name = name or store.__qualname__ + 'WithPathGet'
-
-    # TODO: This is not the best way to handle this. Investigate another way. ######################
-    global_names = set(globals()).union(locals())
-    if name in global_names:
-        raise NameError('That name is already in use')
-    # TODO: ########################################################################################
-
-    store_cls = kv_wrap_persister_cls(store, name=name)
-    store_cls._path_type = path_type
-
-    def __getitem__(self, k):
-        if isinstance(k, self._path_type):
-            return reduce(lambda store, key: store[key], k, self)
-        else:
-            return super(store_cls, self).__getitem__(k)
-
-    store_cls.__getitem__ = __getitem__
-
-    return store_cls
-
-
-def _insert_alias(store, method_name, alias=None):
-    if isinstance(alias, str) and hasattr(store, method_name):
-        setattr(store, alias, getattr(store, method_name))
-
-
-@store_decorator
-def insert_aliases(
-    store=None, *, write=None, read=None, delete=None, list=None, count=None
-):
-    """Insert method aliases of CRUD operations of a store (class or instance).
-    If store is a class, you'll get a copy of the class with those methods added.
-    If store is an instance, the methods will be added in place (no copy will be made).
-
-    Note: If an operation (write, read, delete, list, count) is not specified, no alias will be created for
-    that operation.
-
-    IMPORTANT NOTE: The signatures of the methods the aliases will point to will not change.
-    We say this because, you can call the write method "dump", but you'll have to use it as
-    `store.dump(key, val)`, not `store.dump(val, key)`, which is the signature you're probably used to
-    (it's the one used by json.dump or pickle.dump for example). If you want that familiar interface,
-    using the insert_load_dump_aliases function.
-
-    Args:
-        store: The store to extend with aliases.
-        write: Desired method name for __setitem__
-        read: Desired method name for __getitem__
-        delete: Desired method name for __delitem__
-        list: Desired method name for __iter__
-        count: Desired method name for __len__
-
-    Returns: A store with the desired aliases.
-
-    >>> # Example of extending a class
-    >>> mydict = insert_aliases(dict, write='dump', read='load', delete='rm', list='peek', count='size')
-    >>> s = mydict(true='love')
-    >>> s.dump('friends', 'forever')
-    >>> s
-    {'true': 'love', 'friends': 'forever'}
-    >>> s.load('true')
-    'love'
-    >>> list(s.peek())
-    ['true', 'friends']
-    >>> s.size()
-    2
-    >>> s.rm('true')
-    >>> s
-    {'friends': 'forever'}
-    >>>
-    >>> # Example of extending an instance
-    >>> from collections import UserDict
-    >>> s = UserDict(true='love')  # make (and instance) of a UserDict (can't modify a dict instance)
-    >>> # make aliases of note that you don't need
-    >>> s = insert_aliases(s, write='put', read='retrieve', count='num_of_items')
-    >>> s.put('friends', 'forever')
-    >>> s
-    {'true': 'love', 'friends': 'forever'}
-    >>> s.retrieve('true')
-    'love'
-    >>> s.num_of_items()
-    2
-    """
-
-    if isinstance(store, type):
-        store = type(store.__qualname__, (store,), {})
-    for alias, method_name in _method_name_for.items():
-        _insert_alias(store, method_name, alias=locals().get(alias))
-    return store
-
-
-@store_decorator
-def insert_load_dump_aliases(
-    store=None, *, delete=None, list=None, count=None
-):
-    """Insert load and dump methods, with familiar dump(obj, location) signature.
-
-    Args:
-        store: The store to extend with aliases.
-        delete: Desired method name for __delitem__
-        list: Desired method name for __iter__
-        count: Desired method name for __len__
-
-    Returns: A store with the desired aliases.
-
-    >>> mydict = insert_load_dump_aliases(dict)
-    >>> s = mydict()
-    >>> s.dump(obj='love', key='true')
-    >>> s
-    {'true': 'love'}
-    """
-    store = insert_aliases(
-        store, read='load', delete=delete, list=list, count=count
-    )
-
-    def dump(self, obj, key):
-        return self.__setitem__(key, obj)
-
-    if isinstance(store, type):
-        store.dump = dump
-    else:
-        store.dump = types.MethodType(dump, store)
-
-    return store
-
-
-from typing import TypeVar, Any, Callable
-
-FuncInput = TypeVar('FuncInput')
-FuncOutput = TypeVar('FuncOutput')
-
-
-def constant_output(return_val=None, *args, **kwargs):
-    """Function that returns a constant value no matter what the inputs are.
-    Is meant to be used with functools.partial to create custom versions.
-
-    >>> from functools import partial
-    >>> always_true = partial(constant_output, True)
-    >>> always_true('regardless', 'of', the='input', will='return True')
-    True
-
-    """
-    return return_val
-
-
-@double_up_as_factory
-def condition_function_call(
-    func: Callable[[FuncInput], FuncOutput] = None,
-    *,
-    condition: Callable[[FuncInput], bool] = partial(constant_output, True),
-    callback_if_condition_not_met: Callable[[FuncInput], Any] = partial(
-        constant_output, None
-    ),
-):
-    @wraps(func)
-    def wrapped_func(*args, **kwargs):
-        if condition(*args, **kwargs):
-            return func(*args, **kwargs)
-        else:
-            return callback_if_condition_not_met(*args, **kwargs)
-
-    return wrapped_func
-
-
-from typing import Callable, MutableMapping, Any
-
-Key = Any
-Val = Any
-SetitemCondition = Callable[[MutableMapping, Key, Val], bool]
-
-# @store_decorator
-# def only_allow_writes_that_obey_condition(*,
-#                                           write_condition: SetitemCondition,
-#                                           msg='Write arguments did not match condition.'):
-#     def _only_allow_writes_that_obey_condition(store: MutableMapping):
-#         pass
-
-
-from dol.util import (
-    has_enabled_clear_method,
-    inject_method,
-    _delete_keys_one_by_one,
-)
-
-InjectionValidator = Callable[[type, Callable], bool]
-
-
-@double_up_as_factory
-def ensure_clear_method(store=None, *, clear_method=_delete_keys_one_by_one):
-    """If obj doesn't have an enabled clear method, will add one (a slow one that runs through keys and deletes them"""
-    if not has_enabled_clear_method(store):
-        inject_method(store, clear_method, 'clear')
-    return store
-
-
-@store_decorator
-def add_store_method(
-    store: type,
-    *,
-    method_func,
-    method_name=None,
-    validator: Optional[InjectionValidator] = None,
-):
-    """Add methods to store classes or instances
-
-    :param store: A store type or instance
-    :param method_func: The function of the method to be added
-    :param method_name: The name of the store attribute this function should be written to
-    :param validator: An optional validator. If not None, ``validator(store, method_func)`` will be called.
-        If it doesn't return True, a ``SetattrNotAllowed`` will be raised.
-        Note that ``validator`` can also raise its own exception.
-    :return: A store with the added (or modified) method
-    """
-    method_name = method_name or method_func.__name__
-    if validator is not None:
-        if not validator(store, method_func):
-            raise SetattrNotAllowed(
-                f'Method is not allowed to be set (according to {validator}): {method_func}'
-            )
-    setattr(store, method_name, method_func)
-    return store
-
-
-########## To be deprecated ############################################################################################
-
-# TODO: Factor out the method injection pattern (e.g. __getitem__, __setitem__ and __delitem__ are nearly identical)
-
-
-def filtered_iter(
-    filt: Union[callable, Iterable],
-    store=None,
-    *,
-    name=None,
-    __module__=None,  # TODO: might be able to be deprecated since included in store_decorator
-):
-    """Make a wrapper that will transform a store (class or instance thereof) into a sub-store (i.e. subset of keys).
-
-    Args:
-        filt: A callable or iterable:
-            callable: Boolean filter function. A func taking a key and and returns True iff the key should be included.
-            iterable: The collection of keys you want to filter "in"
-        name: The name to give the wrapped class
-
-    Returns: A wrapper (that then needs to be applied to a store instance or class.
-
-    >>> filtered_dict = filtered_iter(filt=lambda k: (len(k) % 2) == 1)(dict)  # keep only odd length keys
-    >>>
-    >>> s = filtered_dict({'a': 1, 'bb': object, 'ccc': 'a string', 'dddd': [1, 2]})
-    >>>
-    >>> list(s)
-    ['a', 'ccc']
-    >>> 'a' in s  # True because odd (length) key
-    True
-    >>> 'bb' in s  # False because odd (length) key
-    False
-    >>> assert s.get('bb', None) == None
-    >>> len(s)
-    2
-    >>> list(s.keys())
-    ['a', 'ccc']
-    >>> list(s.values())
-    [1, 'a string']
-    >>> list(s.items())
-    [('a', 1), ('ccc', 'a string')]
-    >>> s.get('a')
-    1
-    >>> assert s.get('bb') is None
-    >>> s['x'] = 10
-    >>> list(s.items())
-    [('a', 1), ('ccc', 'a string'), ('x', 10)]
-    >>> try:
-    ...     s['xx'] = 'not an odd key'
-    ...     raise ValueError("This should have failed")
-    ... except KeyError:
-    ...     pass
-    """
-
-    from warnings import warn
-
-    warn(
-        '''filtered_iter is on it's way to be deprecated. Use filt_iter instead.
-     To do so, replace:
-        - imports of filtered_iter by filt_iter
-        - non-keyword arguments by explicitly using arg names, for instance:
-            ```filtered_iter(lambda x: True) -> filt_iter(filt=lambda x: True)```
-    '''
-    )
-    if store is None:
-        if not callable(filt):  # if filt is not a callable...
-            # ... assume it's the collection of keys you want and make a filter function to filter those "in".
-            assert next(
-                iter(filt)
-            ), 'filt should be a callable, or an iterable'
-            keys_that_should_be_filtered_in = set(filt)
-
-            def filt(k):
-                return k in keys_that_should_be_filtered_in
-
-        def wrap(store, name=name, __module__=__module__):
-            if not isinstance(
-                store, type
-            ):  # then consider it to be an instance
-                store_instance = store
-                WrapperStore = filtered_iter(
-                    filt, name=name, __module__=__module__
-                )(Store)
-                return WrapperStore(store_instance)
-            else:  # it's a class we're wrapping
-                collection_cls = store
-                __module__ = __module__ or getattr(
-                    collection_cls, '__module__', None
-                )
-
-                name = name or 'Filtered' + get_class_name(collection_cls)
-                wrapped_cls = type(name, (collection_cls,), {})
-
-                def __iter__(self):
-                    yield from filter(
-                        filt, super(wrapped_cls, self).__iter__()
-                    )
-
-                wrapped_cls.__iter__ = __iter__
-
-                _define_keys_values_and_items_according_to_iter(wrapped_cls)
-
-                def __len__(self):
-                    c = 0
-                    for _ in self.__iter__():
-                        c += 1
-                    return c
-
-                wrapped_cls.__len__ = __len__
-
-                def __contains__(self, k):
-                    if filt(k):
-                        return super(wrapped_cls, self).__contains__(k)
-                    else:
-                        return False
-
-                wrapped_cls.__contains__ = __contains__
-
-                if hasattr(wrapped_cls, '__getitem__'):
-
-                    def __getitem__(self, k):
-                        if filt(k):
-                            return super(wrapped_cls, self).__getitem__(k)
-                        else:
-                            raise KeyError(f'Key not in store: {k}')
-
-                    wrapped_cls.__getitem__ = __getitem__
-
-                if hasattr(wrapped_cls, 'get'):
-
-                    def get(self, k, default=None):
-                        if filt(k):
-                            return super(wrapped_cls, self).get(k, default)
-                        else:
-                            return default
-
-                    wrapped_cls.get = get
-
-                if hasattr(wrapped_cls, '__setitem__'):
-
-                    def __setitem__(self, k, v):
-                        if filt(k):
-                            return super(wrapped_cls, self).__setitem__(k, v)
-                        else:
-                            raise KeyError(f'Key not in store: {k}')
-
-                    wrapped_cls.__setitem__ = __setitem__
-
-                if hasattr(wrapped_cls, '__delitem__'):
-
-                    def __delitem__(self, k):
-                        if filt(k):
-                            return super(wrapped_cls, self).__delitem__(k)
-                        else:
-                            raise KeyError(f'Key not in store: {k}')
-
-                    wrapped_cls.__delitem__ = __delitem__
-
-                if __module__ is not None:
-                    wrapped_cls.__module__ = __module__
-
-                if hasattr(collection_cls, '__doc__'):
-                    wrapped_cls.__doc__ = collection_cls.__doc__
-
-                return wrapped_cls
-
-        return wrap
-    else:
-        return filtered_iter(
-            filt, store=None, name=name, __module__=__module__
-        )(store)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/index.html b/docs/_modules/index.html deleted file mode 100644 index 6e13e51..0000000 --- a/docs/_modules/index.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - Overview: module code — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
- -
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store.html b/docs/_modules/py2store.html deleted file mode 100644 index 2e53c30..0000000 --- a/docs/_modules/py2store.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - - - py2store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store

-"""
-Your portal to many Data Object Layer goodies
-"""
-import os
-from contextlib import suppress
-
-file_sep = os.path.sep
-
-
-
[docs]def kvhead(store, n=1): - """Get the first item of a kv store, or a list of the first n items""" - if n == 1: - for k in store: - return k, store[k] - else: - return [(k, store[k]) for i, k in enumerate(store) if i < n]
- - -
[docs]def ihead(store, n=1): - """Get the first item of an iterable, or a list of the first n items""" - if n == 1: - for item in iter(store): - return item - else: - return [item for i, item in enumerate(store) if i < n]
- - -from py2store.util import lazyprop, partialclass, groupby, regroupby, igroupby - -from py2store.base import ( - Collection, - KvReader, - KvPersister, - Reader, - Persister, - kv_walk, - Store, -) - -from py2store.persisters.local_files import FileReader -from py2store.my.grabbers import ipython_display_val_trans - -from py2store.stores.local_store import ( - LocalStore, - LocalBinaryStore, - LocalTextStore, - LocalPickleStore, - LocalJsonStore, - PickleStore, # consider deprecating and use LocalPickleStore instead? -) -from py2store.stores.local_store import ( - QuickStore, - QuickBinaryStore, - QuickTextStore, - QuickJsonStore, - QuickPickleStore, -) -from py2store.stores.local_store import ( - DirReader, - DirStore, -) - -from py2store.misc import ( - MiscGetter, - MiscGetterAndSetter, - misc_objs, - misc_objs_get, - get_obj, - set_obj, -) - -from py2store.trans import ( - wrap_kvs, - disable_delitem, - disable_setitem, - mk_read_only, - kv_wrap, - cached_keys, - filt_iter, - filtered_iter, - add_path_get, - insert_aliases, - add_ipython_key_completions, - cache_iter, # being deprecated -) -from py2store.access import ( - user_configs_dict, - user_configs, - user_defaults_dict, - user_defaults, -) -from py2store.caching import ( - WriteBackChainMap, - mk_cached_store, - store_cached, - store_cached_with_single_key, - ensure_clear_to_kv_store, - flush_on_exit, - mk_write_cached_store, -) - -from py2store.appendable import appendable - -from py2store.slib.s_zipfile import ( - ZipReader, - ZipFilesReader, - FilesOfZip, - FlatZipFilesReader, - mk_flatzips_store, -) - -from py2store.naming import StrTupleDict -from py2store.paths import mk_relative_path_store - -###### Optionals... ############################################################################## -# TODO: Look into sanity of suppressing both import and module errors -ignore_if_module_not_found = suppress(ModuleNotFoundError, ImportError) - -with ignore_if_module_not_found: - from py2store.access import myconfigs - -with ignore_if_module_not_found: - from py2store.access import mystores - -with ignore_if_module_not_found: - from py2store.stores.s3_store import ( - S3BinaryStore, - S3TextStore, - S3PickleStore, - ) - -# If you want it, import from mongodol (pip installable) directly -# with ignore_if_module_not_found: -# from mongodol.stores import ( -# MongoStore, -# MongoTupleKeyStore, -# MongoAnyKeyStore, -# ) - -with ignore_if_module_not_found: - from py2store.persisters.sql_w_sqlalchemy import ( - SqlDbReader, - SqlTableRowsCollection, - SqlTableRowsSequence, - SqlDbCollection, - SQLAlchemyPersister, - ) - from py2store.stores.sql_w_sqlalchemy import ( - SQLAlchemyStore, - SQLAlchemyTupleStore, - ) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/__init__.html b/docs/_modules/py2store/__init__.html deleted file mode 100644 index d571587..0000000 --- a/docs/_modules/py2store/__init__.html +++ /dev/null @@ -1,333 +0,0 @@ - - - - - - - - py2store.__init__ — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.__init__

-"""
-Your portal to many Data Object Layer goodies
-"""
-import os
-from contextlib import suppress
-
-file_sep = os.path.sep
-
-
-
[docs]def kvhead(store, n=1): - """Get the first item of a kv store, or a list of the first n items""" - if n == 1: - for k in store: - return k, store[k] - else: - return [(k, store[k]) for i, k in enumerate(store) if i < n]
- - -
[docs]def ihead(store, n=1): - """Get the first item of an iterable, or a list of the first n items""" - if n == 1: - for item in iter(store): - return item - else: - return [item for i, item in enumerate(store) if i < n]
- - -from py2store.util import lazyprop, partialclass, groupby, regroupby, igroupby - -from py2store.base import ( - Collection, - KvReader, - KvPersister, - Reader, - Persister, - kv_walk, - Store, -) - -from py2store.persisters.local_files import FileReader -from py2store.my.grabbers import ipython_display_val_trans - -from py2store.stores.local_store import ( - LocalStore, - LocalBinaryStore, - LocalTextStore, - LocalPickleStore, - LocalJsonStore, - PickleStore, # consider deprecating and use LocalPickleStore instead? -) -from py2store.stores.local_store import ( - QuickStore, - QuickBinaryStore, - QuickTextStore, - QuickJsonStore, - QuickPickleStore, -) -from py2store.stores.local_store import ( - DirReader, - DirStore, -) - -from py2store.misc import ( - MiscGetter, - MiscGetterAndSetter, - misc_objs, - misc_objs_get, - get_obj, - set_obj, -) - -from py2store.trans import ( - wrap_kvs, - disable_delitem, - disable_setitem, - mk_read_only, - kv_wrap, - cached_keys, - filt_iter, - filtered_iter, - add_path_get, - insert_aliases, - add_ipython_key_completions, - cache_iter, # being deprecated -) -from py2store.access import ( - user_configs_dict, - user_configs, - user_defaults_dict, - user_defaults, -) -from py2store.caching import ( - WriteBackChainMap, - mk_cached_store, - store_cached, - store_cached_with_single_key, - ensure_clear_to_kv_store, - flush_on_exit, - mk_write_cached_store, -) - -from py2store.appendable import appendable - -from py2store.slib.s_zipfile import ( - ZipReader, - ZipFilesReader, - FilesOfZip, - FlatZipFilesReader, - mk_flatzips_store, -) - -from py2store.naming import StrTupleDict -from py2store.paths import mk_relative_path_store - -###### Optionals... ############################################################################## -# TODO: Look into sanity of suppressing both import and module errors -ignore_if_module_not_found = suppress(ModuleNotFoundError, ImportError) - -with ignore_if_module_not_found: - from py2store.access import myconfigs - -with ignore_if_module_not_found: - from py2store.access import mystores - -with ignore_if_module_not_found: - from py2store.stores.s3_store import ( - S3BinaryStore, - S3TextStore, - S3PickleStore, - ) - -# If you want it, import from mongodol (pip installable) directly -# with ignore_if_module_not_found: -# from mongodol.stores import ( -# MongoStore, -# MongoTupleKeyStore, -# MongoAnyKeyStore, -# ) - -with ignore_if_module_not_found: - from py2store.persisters.sql_w_sqlalchemy import ( - SqlDbReader, - SqlTableRowsCollection, - SqlTableRowsSequence, - SqlDbCollection, - SQLAlchemyPersister, - ) - from py2store.stores.sql_w_sqlalchemy import ( - SQLAlchemyStore, - SQLAlchemyTupleStore, - ) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/access.html b/docs/_modules/py2store/access.html deleted file mode 100644 index bddd07a..0000000 --- a/docs/_modules/py2store/access.html +++ /dev/null @@ -1,533 +0,0 @@ - - - - - - - - py2store.access — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.access

-"""
-Utils to load stores from store specifications.
-Includes the logic to allow configurations (and defaults) to be parametrized by external environmental
-variables and files.
-
-Every data-sourced problem has it's problem-relevant stores. Once you get your stores right, along with the
-right access credentials, indexing, serialization, caching, filtering etc. you'd like to be able to name, save
-and/or share this specification, and easily get access to it later on.
-
-Here are tools to help you out.
-
-There are two main key-value stores: One for configurations the user wants to reuse, and the other for the user's
-desired defaults. Both have the same structure:
-    * first level key: Name of the resource (should be a valid python variable name)
-    * The reminder is more or less free form (until the day we lay out some schemas for this)
-
-The system will look for the specification of user_configs and user_defaults in a json file.
-The filepath to this json file can specified in environment variables
-    PY2STORE_CONFIGS_JSON_FILEPATH and PY2STORE_DEFAULTS_JSON_FILEPATH
-respectively.
-By default, they are:
-    ~/.py2store_configs.json and ~/.py2store_defaults.json
-respectively.
-"""
-import os
-import importlib
-from warnings import warn
-from functools import reduce
-from operator import getitem
-from py2store.util import str_to_var_str
-from py2store.sources import DictAttr
-
-FAK = '$fak'
-
-
-# TODO: Make a config_utils.py module to centralize config tools (configs for access is just one -- serializers another)
-# TODO: Integrate (external because not standard lib) other safer tools for secrets, such as:
-#  https://github.com/SimpleLegal/pocket_protector
-
-
-
[docs]def getenv(name, default=None): - """Like os.getenv, but removes a suffix \\r character if present (problem with some env var systems)""" - v = os.getenv(name, default) - if v.endswith('\r'): - return v[:-1] - else: - return v
- - -def assert_callable(f: callable) -> callable: - assert callable(f), f'Is not callable: {f}' - return f - - -
[docs]def dotpath_to_obj(dotpath): - """Loads and returns the object referenced by the string DOTPATH_TO_MODULE.OBJ_NAME""" - *module_path, obj_name = dotpath.split('.') - if len(module_path) > 0: - return getattr( - importlib.import_module('.'.join(module_path)), obj_name - ) - else: - return importlib.import_module(obj_name)
- - -
[docs]def dotpath_to_func(f: (str, callable)) -> callable: - """Loads and returns the function referenced by f, - which could be a callable or a DOTPATH_TO_MODULE.FUNC_NAME dotpath string to one. - """ - - if isinstance(f, str): - if '.' in f: - *module_path, func_name = f.split('.') - f = getattr( - importlib.import_module('.'.join(module_path)), func_name - ) - else: - f = getattr(importlib.import_module('py2store'), f) - - return assert_callable(f)
- - -
[docs]def compose(*functions): - """Make a function that is the composition of the input functions""" - return reduce(lambda f, g: lambda x: f(g(x)), functions, lambda x: x)
- - -
[docs]def dflt_func_loader(f) -> callable: - """Loads and returns the function referenced by f, - which could be a callable or a DOTPATH_TO_MODULE.FUNC_NAME dotpath string to one, or a pipeline of these - """ - if isinstance(f, str) or callable(f): - return dotpath_to_func(f) - else: - return compose(*map(dflt_func_loader, f))
- - -def _fakit(f: callable, a: (tuple, list), k: dict): - return f(*(a or ()), **(k or {})) - - -def fakit_from_dict(d, func_loader=assert_callable): - return _fakit(func_loader(d['f']), a=d.get('a', ()), k=d.get('k', {})) - - -def fakit_from_tuple( - t: (tuple, list), func_loader: callable = dflt_func_loader -): - f = func_loader(t[0]) - a = () - k = {} - assert len(t) in { - 1, - 2, - 3, - }, 'A tuple fak must be of length 1, 2, or 3. No more, no less.' - if len(t) > 1: - if isinstance(t[1], dict): - k = t[1] - else: - assert isinstance( - t[1], (tuple, list) - ), 'argument specs should be dict, tuple, or list' - a = t[1] - if len(t) > 2: - if isinstance(t[2], dict): - assert not k, 'can only have one kwargs' - k = t[2] - else: - assert isinstance( - t[2], (tuple, list) - ), 'argument specs should be dict, tuple, or list' - assert not a, 'can only have one args' - a = t[2] - return _fakit(f, a, k) - - -
[docs]def fakit(fak, func_loader=dflt_func_loader): - """Execute a fak with given f, a, k and function loader. - - Essentially returns func_loader(f)(*a, **k) - - Args: - fak: A (f, a, k) specification. Could be a tuple or a dict (with 'f', 'a', 'k' keys). All but f are optional. - func_loader: A function returning a function. This is where you specify any validation of func specification f, - and/or how to get a callable from it. - - Returns: A python object. - """ - - if isinstance(fak, dict): - return fakit_from_dict(fak, func_loader=func_loader) - else: - assert isinstance( - fak, (tuple, list) - ), 'fak should be dict, tuple, or list' - return fakit_from_tuple(fak, func_loader=func_loader)
- - -fakit.from_dict = fakit_from_dict -fakit.from_tuple = fakit_from_tuple - -user_configs_dict = {} -user_defaults_dict = {} -user_configs = None -user_defaults = None -mystores = None - -try: - import json - - user_configs_dirpath = os.path.expanduser( - getenv('PY2STORE_CONFIGS_DIR', '~/.py2store_configs') - ) - my_configs_dirname = os.path.expanduser( - getenv('MY_PY2STORE_DIR_NAME', 'my') - ) - myconfigs_dirpath = os.path.join(user_configs_dirpath, my_configs_dirname) - - if os.path.isdir(user_configs_dirpath): - - def directory_json_items(): - for f in filter( - lambda x: x.endswith('.json'), os.listdir(user_configs_dirpath) - ): - filepath = os.path.join(user_configs_dirpath, f) - name, _ = os.path.splitext(f) - try: - d = json.load(open(filepath)) - yield str_to_var_str(name), d - except json.JSONDecodeError: - warn( - f"This json file couldn't be json-decoded: {filepath}" - ) - except Exception: - warn( - f'Unknown error when trying to json.load this file: {filepath}' - ) - - user_configs = DictAttr(**{k: v for k, v in directory_json_items()}) - - from py2store.base import KvStore - from py2store.stores.local_store import ( - LocalJsonStore, - LocalBinaryStore, - ) - from py2store.trans import wrap_kvs - from py2store.misc import MiscStoreMixin - from functools import wraps - - if os.path.isdir(myconfigs_dirpath): - from py2store.mixins import OverWritesNotAllowedMixin - - @OverWritesNotAllowedMixin.wrap - class MyConfigs(MiscStoreMixin, LocalBinaryStore): - key_sep = ':' - - @wraps(LocalBinaryStore) - def __init__(self, *args, **kwargs): - LocalBinaryStore.__init__(self, *args, **kwargs) - self._init_args_kwargs = (args, kwargs) - if len(args) > 0: - self.dirpath = args[0] - elif len(kwargs) > 0: - self.dirpath = kwargs[next(iter(kwargs))] - - def refresh(self): - """Sometimes you add a file, and you want to make sure your myconfigs sees it. - refresh() does that for you. It reinitializes the reader.""" - args, kwargs = self._init_args_kwargs - self.__init__(*args, **kwargs) - - def get(self, k, default=None): - try: - return self[k] - except KeyError: - return default - - def __getitem__(self, k): - try: - if self.key_sep not in k: - return super().__getitem__(k) - else: - return reduce(getitem, k.split(self.key_sep), self) - except KeyError: - raise KeyError( - f"The '{k}' key wasn't found. " - f'What this probably is, is that you need ' - f"a file named '{k}' in your {self.dirpath} folder. Do that and try again." - ) - - def get_config_value(self, k, path=None): - v = self.get(k) - if path is None: - return v - else: - if path in v: - return v[path] - else: - raise KeyError(f"I don't see any {path} in {k}") - - @property - def rootdir(self): - return self._prefix - - def __delitem__(self, k): - raise NotImplementedError( - 'Deletion was disabled. ' - 'MyConfigs wants to keep your configs safe, so if you want to delete this config, ' - 'do it another way (like manually).' - ) - - myconfigs = MyConfigs(myconfigs_dirpath) - myconfigs.dirpath = myconfigs_dirpath - else: - warn( - f'''The py2store-myconfigs directory wasn't found: {myconfigs_dirpath} - If you want to have all the cool functionality of `myconfigs`, you should make this directory, - and put stuff in it. Here's to make it easy for you to do it. Go to a terminal and run this: - mkdir {myconfigs_dirpath} - ''' - ) - - class MyStores(KvStore): - func_loader = staticmethod(dflt_func_loader) - - def _obj_of_data(self, data): - if FAK in data: - return fakit(data[FAK], self.func_loader) - else: - msg = 'Case not handled by MyStores' - if isinstance(data, dict): - raise ValueError(f'{msg}: keys: {list(data.keys())}') - else: - raise ValueError(f'{msg}: type: {type(data)}') - - @property - def configs(self): - return self.store - - def without_json_ext(_id): - assert _id.endswith('.json'), 'Should end with .json' - return _id[: -len('.json')] - - def add_json_ext(k): - return k + '.json' - - ExtLessJsonStore = wrap_kvs( - LocalJsonStore, - name='ExtLessJsonStore', - key_of_id=without_json_ext, - id_of_key=add_json_ext, - ) - - stores_json_path_format = os.path.join( - user_configs_dirpath, 'stores', 'json', '{}.json' - ) - mystores = MyStores(ExtLessJsonStore(stores_json_path_format)) - from py2store.trans import add_ipython_key_completions - - mystores = add_ipython_key_completions(mystores) - mystores.dirpath = user_configs_dirpath - - else: - warn( - f"The configs directory wasn't found (please make it): {user_configs_dirpath}" - ) - user_configs_filepath = os.path.expanduser( - getenv( - 'PY2STORE_CONFIGS_JSON_FILEPATH', '~/.py2store_configs.json' - ) - ) - if os.path.isfile(user_configs_filepath): - user_configs_dict = json.load(open(user_configs_filepath)) - user_configs = DictAttr( - **{str_to_var_str(k): v for k, v in user_configs_dict.items()} - ) - - user_defaults_filepath = os.path.expanduser( - getenv('PY2STORE_DEFAULTS_JSON_FILEPATH', '~/.py2store_defaults.json') - ) - if os.path.isfile(user_defaults_filepath): - user_defaults_dict = json.load(open(user_defaults_filepath)) - user_defaults = DictAttr( - **{str_to_var_str(k): v for k, v in user_defaults_dict.items()} - ) - -except Exception as e: - warn( - f'There was an exception when trying to get configs and defaults: {e}' - ) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/appendable.html b/docs/_modules/py2store/appendable.html deleted file mode 100644 index 88da814..0000000 --- a/docs/_modules/py2store/appendable.html +++ /dev/null @@ -1,699 +0,0 @@ - - - - - - - - py2store.appendable — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.appendable

-"""
-Tools to add append-functionality to key-val stores. The main function is
-    `appendable_store_cls = add_append_functionality_to_store_cls(store_cls, item2kv, ...)`
-You give it the `store_cls` you want to sub class, and a item -> (key, val) function, and you get a store (subclass) that
-has a `store.append(item)` method. Also includes an extend method (that just called appends in a loop.
-
-See add_append_functionality_to_store_cls docs for examples.
-"""
-
-import time
-import types
-
-from py2store.trans import store_decorator
-
-
-
[docs]def define_extend_as_seq_of_appends(obj): - """Inject an extend method in obj that will used append method. - - Args: - obj: Class (type) or instance of an object that has an "append" method. - - Returns: The obj, but with that extend method. - - >>> class A: - ... def __init__(self): - ... self.t = list() - ... def append(self, item): - ... self.t.append(item) - ... - >>> AA = define_extend_as_seq_of_appends(A) - >>> a = AA() - >>> a.extend([1,2,3]) - >>> a.t - [1, 2, 3] - >>> a.extend([10, 20]) - >>> a.t - [1, 2, 3, 10, 20] - >>> a = A() - >>> a = define_extend_as_seq_of_appends(a) - >>> a.extend([1,2,3]) - >>> a.t - [1, 2, 3] - >>> a.extend([10, 20]) - >>> a.t - [1, 2, 3, 10, 20] - - """ - assert hasattr( - obj, "append" - ), f"Your object needs to have an append method! Object was: {obj}" - - def extend(self, items): - for item in items: - self.append(item) - - if isinstance(obj, type): - obj = type(obj.__name__, (obj,), {}) - obj.extend = extend - else: - obj.extend = types.MethodType(extend, obj) - return obj
- - -######################################################################################################################## - -class NotSpecified: - pass - - -
[docs]class mk_item2kv_for: - """A bunch of functions to make item2kv functions - - A few examples (see individual methods' docs for more examples) - - >>> # item_to_key - >>> item2kv = mk_item2kv_for.item_to_key(item2key=lambda item: item['L'] ) - >>> item2kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let', {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> - >>> # utc_key - >>> import time - >>> item2key = mk_item2kv_for.utc_key() - >>> k, v = item2key('some data') - >>> assert abs(time.time() - k) < 0.01 # which asserts that k is indeed a (current) utc timestamp - >>> assert v == 'some data' # just the item itself - >>> - >>> # item_to_key_params_and_val - >>> item_to_kv = mk_item2kv_for.item_to_key_params_and_val(lambda x: ((x['L'], x['I']), x['G']), '{}/{}') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let/it', 'go') - >>> - >>> # fields - >>> item_to_kv = mk_item2kv_for.fields(['L', 'I']) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ({'L': 'let', 'I': 'it'}, {'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), keep_field_in_value=True) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) # note the order of the key is not ('G', 'L')... - ({'L': 'let', 'G': 'go'}, {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), key_as_tuple=True) # but ('G', 'L') order is respected here - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - (('go', 'let'), {'I': 'it'}) - """ - -
[docs] @staticmethod - def item_to_key(item2key): - """Make item2kv from a item2key function (the value will be the item itself). - - Args: - item2key: an item -> key function - - Returns: an item -> (key, val) function - - >>> item2key = lambda item: item['G'] # use value of 'L' as the key - >>> item2key({'L': 'let', 'I': 'it', 'G': 'go'}) - 'go' - >>> item2kv = mk_item2kv_for.item_to_key(item2key) - >>> item2kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('go', {'L': 'let', 'I': 'it', 'G': 'go'}) - """ - - def item2kv(item): - return item2key(item), item - - return item2kv
- -
[docs] @staticmethod - def field(field, keep_field_in_value=True, dflt_if_missing=NotSpecified): - """item2kv that uses a specific key of a (mapping) item as the key - - Note: If keep_field_in_value=False, the field will be popped OUT of the item. - If that's not the desired effect, one should feed copies of the items (e.g. map(dict.copy, items)) - - :param field: The field (value) to use as the returned key - :param keep_field_in_value: Whether to leave the field in the item. If False, will pop it out - :param dflt_if_missing: If specified (even None) will use the specified key as the key, if the field is missig - :return: A item2kv function - - >>> item2kv = mk_item2kv_for.field('G') - >>> item2kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('go', {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> item2kv = mk_item2kv_for.field('G', keep_field_in_value=False) - >>> item2kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('go', {'L': 'let', 'I': 'it'}) - >>> item2kv = mk_item2kv_for.field('G', dflt_if_missing=None) - >>> item2kv({'L': 'let', 'I': 'it', 'DIE': 'go'}) - (None, {'L': 'let', 'I': 'it', 'DIE': 'go'}) - - """ - if dflt_if_missing is NotSpecified: - if keep_field_in_value: - def item2kv(item): - return item[field], item - else: - def item2kv(item): - return item.pop(field), item - else: - if keep_field_in_value: - def item2kv(item): - return item.get(field, dflt_if_missing), item - else: - def item2kv(item): - return item.pop(field, dflt_if_missing), item - - return item2kv
- -
[docs] @staticmethod - def utc_key(offset_s=0.0): - """Make an item2kv function that uses the current time as the key, and the unchanged item as a value. - The offset_s, which is added to the output key, can be used, for example, to align to another system's clock, - or to get a more accurate timestamp of an event. - - Use case for offset_s: - * Align to another system's clock - * Get more accurate timestamping of an event. For example, in situations where the item is a chunk of live - streaming data and we want the key (timestamp) to represent the timestamp of the beginning of the chunk. - Without an offset_s, the timestamp would be the timestamp after the last byte of the chunk was produced, - plus the time it took to reach the present function. If we know the data production rate (e.g. sample rate) - and the average lag to get to the present function, we can get a more accurate timestamp for the beginning - of the chunk - - Args: - offset_s: An offset (in seconds, possibly negative) to add to the current time. - - Returns: an item -> (current_utc_s, item) function - - >>> import time - >>> item2key = mk_item2kv_for.utc_key() - >>> k, v = item2key('some data') - >>> assert abs(time.time() - k) < 0.01 # which asserts that k is indeed a (current) utc timestamp - >>> assert v == 'some data' # just the item itself - - """ - if ( - offset_s == 0.0 - ): # splitting for extra speed (important in real time apps) - - def item2kv(item): - return time.time(), item - - else: - - def item2kv(item): - return time.time() + offset_s, item - - return item2kv
- -
[docs] @staticmethod - def item_to_key_params_and_val(item_to_key_params_and_val, key_str_format): - """Make item2kv from a function that produces key_params and val, - and a key_template that will produce a string key from the key_params - - Args: - item_to_key_params_and_val: an item -> (key_params, val) function - key_str_format: A string format such that - key_str_format.format(*key_params) or - key_str_format.format(**key_params) - will produce the desired key string - - Returns: an item -> (key, val) function - - >>> # Using tuple key params with unnamed string format fields - >>> item_to_kv = mk_item2kv_for.item_to_key_params_and_val(lambda x: ((x['L'], x['I']), x['G']), '{}/{}') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let/it', 'go') - >>> - >>> # Using dict key params with named string format fields - >>> item_to_kv = mk_item2kv_for.item_to_key_params_and_val( - ... lambda x: ({'second': x['L'], 'first': x['G']}, x['I']), '{first}_{second}') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('go_let', 'it') - """ - - def item2kv(item): - key_params, val = item_to_key_params_and_val(item) - if isinstance(key_params, dict): - return key_str_format.format(**key_params), val - else: - return key_str_format.format(*key_params), val - - return item2kv
- -
[docs] @staticmethod - def fields(fields, keep_field_in_value=False, key_as_tuple=False): - """Make item2kv from specific fields of a Mapping (i.e. dict-like object) item. - - Note: item2kv will not mutate item (even if keep_field_in_value=False). - - Args: - fields: The sequence (list, tuple, etc.) of item fields that should be used to create the key. - keep_field_in_value: Set to True to return the item as is, as the value - key_as_tuple: Set to True if you want keys to be tuples (note that the fields order is important here!) - - Returns: an item -> (item[fields], item[not in fields]) function - - >>> item_to_kv = mk_item2kv_for.fields('L') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ({'L': 'let'}, {'I': 'it', 'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(['L', 'I']) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ({'L': 'let', 'I': 'it'}, {'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), keep_field_in_value=True) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) # note the order of the key is not ('G', 'L')... - ({'L': 'let', 'G': 'go'}, {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), key_as_tuple=True) # but ('G', 'L') order is respected here - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - (('go', 'let'), {'I': 'it'}) - - """ - if isinstance(fields, str): - fields_set = {fields} - fields = (fields,) - else: - fields_set = set(fields) - - def item2kv(item): - if keep_field_in_value: - key = dict() - for k, v in item.items(): - if k in fields_set: - key[k] = v - val = item - else: - key = dict() - val = dict() - for k, v in item.items(): - if k in fields_set: - key[k] = v - elif not keep_field_in_value: - val[k] = v - - if key_as_tuple: - return tuple(key[f] for f in fields), val - else: - return key, val - - return item2kv
- - -
[docs]@store_decorator -def appendable( - store_cls=None, *, item2kv, -): - """Makes a new class with append (and consequential extend) methods - - Args: - store_cls: The store class to subclass - item2kv: The function that produces a (key, val) pair from an item - new_store_name: The name to give the new class (default will be 'Appendable' + store_cls.__name__) - - Returns: A subclass of store_cls with two additional methods: append, and extend. - - - >>> item_to_kv = lambda item: (item['L'], item) # use value of 'L' as the key, and value is the item itself - >>> MyStore = appendable(dict, item2kv=item_to_kv) - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [('let', {'L': 'let', 'I': 'it', 'G': 'go'})] - - Use mk_item2kv.from_item_to_key_params_and_val with tuple key params - - >>> item_to_kv = appendable.mk_item2kv_for.item_to_key_params_and_val(lambda x: ((x['L'], x['I']), x['G']), '{}/{}') - >>> MyStore = appendable(item2kv=item_to_kv)(dict) # showing the append(...)(store) form - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [('let/it', 'go')] - - Use mk_item2kv.from_item_to_key_params_and_val with dict key params - - >>> item_to_kv = appendable.mk_item2kv_for.item_to_key_params_and_val( - ... lambda x: ({'L': x['L'], 'G': x['G']}, x['I']), '{G}_{L}') - >>> @appendable(item2kv=item_to_kv) # showing the @ form - ... class MyStore(dict): - ... pass - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [('go_let', 'it')] - - Use mk_item2kv.fields to get a tuple key from item fields, - defining the sub-dict of the remaining fields to be the value. - Also showing here how you can decorate the instance itself. - - >>> item_to_kv = appendable.mk_item2kv_for.fields(['G', 'L'], key_as_tuple=True) - >>> d = {} - >>> s = appendable(d, item2kv=item_to_kv) - >>> s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [(('go', 'let'), {'I': 'it'})] - """ - - def append(self, item): - k, v = item2kv(item) - self[k] = v - - def extend(self, items): - for item in items: - self.append(item) - - return type( - "Appendable" + store_cls.__name__, - (store_cls,), - {"append": append, "extend": extend} - )
- - -add_append_functionality_to_store_cls = appendable # for back compatibility - -appendable.mk_item2kv_for = mk_item2kv_for # adding as attribute for convenient access - -from collections.abc import Sequence -from typing import Iterable, Optional - -NotAVal = type( - "NotAVal", (), {} -)() # singleton instance to distinguish from None - - -# -# class FixedSizeStack(Sequence): -# """A finite Sequence that can have no more than one element. -# -# >>> t = FixedSizeStack(maxsize=1) -# >>> assert len(t) == 0 -# >>> -# >>> t.append('something') -# >>> assert len(t) == 1 -# >>> assert t[0] == 'something' -# >>> -# >>> t.append('something else') -# >>> assert len(t) == 1 # still only one item -# >>> assert t[0] == 'something' # still the same item -# -# Not that we'd ever these methods of FirstAppendOnly, -# but know that FirstAppendOnly is a collection.abc.Sequence, so... -# -# >>> t[:1] == t[:10] == t[::-1] == t[::-10] == t[0:2:10] == list(reversed(t)) == ['something'] -# True -# >>> -# >>> assert t.count('something') == 1 -# >>> assert t.index('something') == 0 -# -# """ -# -# def __init__(self, iterable: Optional[Iterable] = None, *, maxsize: int): -# self.maxsize = maxsize -# self.data = [NotAVal] * maxsize -# # self.data = (isinstance(iterable, Iterable) and list(iterable)) or [] -# # if iterable is not None: -# # pass -# self.cursor = 0 -# -# def append(self, v): -# if self.cursor < self.maxsize: -# self.data[self.cursor] = v -# self.cursor += 1 -# -# def __len__(self): -# return self.cursor -# -# def __getitem__(self, k): -# if isinstance(k, int): -# if k < self.cursor: -# return self.data[k] -# else: -# raise IndexError( -# f"There are only {len(self)} items: You asked for self[{k}]." -# ) -# elif isinstance(k, slice): -# return self.data[: self.cursor][k] -# else: -# raise IndexError( -# f"A {self.__class__} instance can only have one value, or none at all." -# ) -# - -
[docs]class FirstAppendOnly(Sequence): - """A finite Sequence that can have no more than one element. - - >>> t = FirstAppendOnly() - >>> assert len(t) == 0 - >>> - >>> t.append('something') - >>> assert len(t) == 1 - >>> assert t[0] == 'something' - >>> - >>> t.append('something else') - >>> assert len(t) == 1 # still only one item - >>> assert t[0] == 'something' # still the same item - >>> - >>> # Not that we'd ever these methods of FirstAppendOnly, but know that FirstAppendOnly is a collection.abc.Sequence, so... - >>> t[:1] == t[:10] == t[::-1] == t[::-10] == t[0:2:10] == list(reversed(t)) == ['something'] - <stdin>:1: RuntimeWarning: coroutine 'AioFileBytesPersister.asetitem' was never awaited - RuntimeWarning: Enable tracemalloc to get the object allocation traceback - True - >>> - >>> t.count('something') == 1 - True - >>> t.index('something') == 0 - True - """ - - def __init__(self): - self.val = NotAVal - - def append(self, v): - if self.val == NotAVal: - self.val = v - - def __len__(self): - return int(self.val != NotAVal) - - def __getitem__(self, k): - if len(self) == 0: - raise IndexError( - f"There are no items in this {self.__class__} instance" - ) - elif k == 0: - return self.val - elif isinstance(k, slice): - return [self.val][k] - else: - raise IndexError( - f"A {self.__class__} instance can only have one value, or none at all." - )
- - # @staticmethod - # def from - -# def add_append_functionality_to_str_key_store(store_cls, -# item_to_key_params_and_val, -# key_template=None, -# new_store_name=None): -# def item_to_kv(item): -# nonlocal key_template -# if key_template is None: -# key_params, _ = item_to_key_params_and_val(item) -# key_template = path_sep.join('{{{}}}'.format(p) for p in key_params) -# key_params, val = item_to_key_params_and_val(item) -# return key_template.format(**key_params), val -# -# return add_append_functionality_to_store_cls(store_cls, item_to_kv, new_store_name) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/base.html b/docs/_modules/py2store/base.html deleted file mode 100644 index 69ee201..0000000 --- a/docs/_modules/py2store/base.html +++ /dev/null @@ -1,967 +0,0 @@ - - - - - - - - py2store.base — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.base

-"""
-Base classes for making stores.
-In the language of the collections.abc module, a store is a MutableMapping that is configured to work with a specific
-representation of keys, serialization of objects (python values), and persistence of the serialized data.
-
-That is, stores offer the same interface as a dict, but where the actual implementation of writes, reads, and listing
-are configurable.
-
-Consider the following example. You're store is meant to store waveforms as wav files on a remote server.
-Say waveforms are represented in python as a tuple (wf, sr), where wf is a list of numbers and sr is the sample
-rate, an int). The __setitem__ method will specify how to store bytes on a remote server, but you'll need to specify
-how to SERIALIZE (wf, sr) to the bytes that constitute that wav file: _data_of_obj specifies that.
-You might also want to read those wav files back into a python (wf, sr) tuple. The __getitem__ method will get
-you those bytes from the server, but the store will need to know how to DESERIALIZE those bytes back into a python
-object: _obj_of_data specifies that
-
-Further, say you're storing these .wav files in /some/folder/on/the/server/, but you don't want the store to use
-these as the keys. For one, it's annoying to type and harder to read. But more importantly, it's an irrelevant
-implementation detail that shouldn't be exposed. THe _id_of_key and _key_of_id pair are what allow you to
-add this key interface layer.
-
-These key converters object serialization methods default to the identity (i.e. they return the input as is).
-This means that you don't have to implement these as all, and can choose to implement these concerns within
-the storage methods themselves.
-"""
-
-from collections.abc import Collection as CollectionABC
-from collections.abc import Mapping, MutableMapping
-from typing import Any, Iterable, Tuple
-
-from py2store.util import wraps
-
-# from functools import wraps
-
-Key = Any
-Val = Any
-Id = Any
-Data = Any
-Item = Tuple[Key, Val]
-KeyIter = Iterable[Key]
-ValIter = Iterable[Val]
-ItemIter = Iterable[Item]
-
-
-class AttrNames:
-    CollectionABC = {"__len__", "__iter__", "__contains__"}
-    Mapping = CollectionABC | {
-        "keys",
-        "get",
-        "items",
-        "__reversed__",
-        "values",
-        "__getitem__",
-    }
-    MutableMapping = Mapping | {
-        "setdefault",
-        "pop",
-        "popitem",
-        "clear",
-        "update",
-        "__delitem__",
-        "__setitem__",
-    }
-
-    Collection = CollectionABC | {"head"}
-    KvReader = (Mapping | {"head"}) - {"__reversed__"}
-    KvPersister = (MutableMapping | {"head"}) - {"__reversed__"} - {"clear"}
-
-
-
[docs]class Collection(CollectionABC): - """The same as collections.abc.Collection, with some modifications: - - Addition of a ``head`` - """ - - def __contains__(self, x) -> bool: - """ - Check if collection of keys contains k. - Note: This method loops through all contents of collection to see if query element exists. - Therefore it may not be efficient, and in most cases, a method specific to the case should be used. - :return: True if k is in the collection, and False if not - """ - for existing_x in self.__iter__(): - if existing_x == x: - return True - return False - - def __len__(self) -> int: - """ - Number of elements in collection of keys. - Note: This method iterates over all elements of the collection and counts them. - Therefore it is not efficient, and in most cases should be overridden with a more efficient version. - :return: The number (int) of elements in the collection of keys. - """ - # Note: Found that sum(1 for _ in self.__iter__()) was slower for small, slightly faster for big inputs. - count = 0 - for _ in self.__iter__(): - count += 1 - return count - - def head(self): - if hasattr(self, "items"): - return next(iter(self.items())) - else: - return next(iter(self))
- - -# KvCollection = Collection # alias meant for back-compatibility. Would like to deprecated - - -# def getitem_based_contains(self, x) -> bool: -# """ -# Check if collection of keys contains k. -# Note: This method actually fetches the contents for k, returning False if there's a key error trying to do so -# Therefore it may not be efficient, and in most cases, a method specific to the case should be used. -# :return: True if k is in the collection, and False if not -# """ -# -# try: -# self.__getitem__(k) -# return True -# except KeyError: -# return False - - -
[docs]class KvReader(Collection, Mapping): - """Acts as a Mapping abc, but with default __len__ (implemented by counting keys) - and head method to get the first (k, v) item of the store""" - - def head(self): - for k, v in self.items(): - return k, v - - def __reversed__(self): - """The __reversed__ is disabled at the base, but can be re-defined in subclasses. - Rationale: KvReader is meant to wrap a variety of storage backends or key-value perspectives thereof. - Not all of these would have a natural or intuitive order nor do we want to maintain one systematically. - - If you need a reversed list, here's one way to do it, but note that it - depends on how self iterates, which is not even assured to be consistent at every call: - ``` - reversed = list(self)[::-1] - ``` - - If the keys are comparable, therefore sortable, another natural option would be: - ``` - reversed = sorted(self)[::-1] - ``` - """ - raise NotImplementedError(__doc__)
- - -Reader = KvReader # alias - - -# TODO: Should we really be using MutableMapping if we're disabling so many of it's methods? -# TODO: Wishful thinking: Define store type so the type is defined by it's methods, not by subclassing. -
[docs]class KvPersister(KvReader, MutableMapping): - """ Acts as a MutableMapping abc, but disabling the clear and __reversed__ method, - and computing __len__ by iterating over all keys, and counting them. - - Note that KvPersister is a MutableMapping, and as such, is dict-like. - But that doesn't mean it's a dict. - - For instance, consider the following code: - ``` - s = SomeKvPersister() - s['a']['b'] = 3 - ``` - If `s` is a dict, this would have the effect of adding a ('b', 3) item under 'a'. - But in the general case, this might - - fail, because the `s['a']` doesn't support sub-scripting (doesn't have a `__getitem__`) - - or, worse, will pass silently but not actually persist the write as expected (e.g. LocalFileStore) - - Another example: `s.popitem()` will pop a `(k, v)` pair off of the `s` store. - That is, retrieve the `v` for `k`, delete the entry for `k`, and return a `(k, v)`. - Note that unlike modern dicts which will return the last item that was stored - -- that is, LIFO (last-in, first-out) order -- for KvPersisters, - there's no assurance as to what item will be, since it will depend on the backend storage system - and/or how the persister was implemented. - - """ - -
[docs] def clear(self): - """The clear method is disabled to make dangerous difficult. - You don't want to delete your whole DB - If you really want to delete all your data, you can do so by doing something like this: - ``` - for k in self: - try: - del self[k] - except KeyError: - pass - ``` - """ - raise NotImplementedError(__doc__)
- - # # TODO: Tests and documentation demos needed. - # def popitem(self): - # """pop a (k, v) pair off of the store. - # That is, retrieve the v for k, delete the entry for k, and return a (k, v) - # Note that unlike modern dicts which will return the last item that was stored - # -- that is, LIFO (last-in, first-out) order -- for KvPersisters, - # there's no assurance as to what item will be, since it will depend on the backend storage system - # and/or how the persister was implemented. - # :return: - # """ - # return super(KvPersister, self).popitem() - - -Persister = KvPersister # alias for back-compatibility - - -# TODO: Make identity_func "identifiable". If we use the following one, we can use == to detect it's use, -# TODO: ... but there may be a way to annotate, register, or type any identity function so it can be detected. -def identity_func(x): - return x - - -static_identity_method = staticmethod(identity_func) - - -class NoSuchItem: - pass - - -no_such_item = NoSuchItem() - - -def cls_wrap(cls, obj): - if isinstance(obj, type): - class Wrap(cls): - @wraps(obj.__init__) - def __init__(self, *args, **kwargs): - wrapped = obj(*args, **kwargs) - super().__init__(wrapped) - - return Wrap - else: - return cls(obj) - - -
[docs]class Store(KvPersister): - """ - By store we mean key-value store. This could be files in a filesystem, objects in s3, or a database. Where and - how the content is stored should be specified, but StoreInterface offers a dict-like interface to this. - :: - __getitem__ calls: _id_of_key _obj_of_data - __setitem__ calls: _id_of_key _data_of_obj - __delitem__ calls: _id_of_key - __iter__ calls: _key_of_id - - - >>> # Default store: no key or value conversion ################################################ - >>> s = Store() - >>> s['foo'] = 33 - >>> s['bar'] = 65 - >>> assert list(s.items()) == [('foo', 33), ('bar', 65)] - >>> assert list(s.store.items()) == [('foo', 33), ('bar', 65)] # see that the store contains the same thing - >>> - >>> ################################################################################################ - >>> # Now let's make stores that have a key and value conversion layer ############################# - >>> # input keys will be upper cased, and output keys lower cased ################################## - >>> # input values (assumed int) will be converted to ascii string, and visa versa ################# - >>> ################################################################################################ - >>> - >>> def test_store(s): - ... s['foo'] = 33 # write 33 to 'foo' - ... assert 'foo' in s # __contains__ works - ... assert 'no_such_key' not in s # __nin__ works - ... s['bar'] = 65 # write 65 to 'bar' - ... assert len(s) == 2 # there are indeed two elements - ... assert list(s) == ['foo', 'bar'] # these are the keys - ... assert list(s.keys()) == ['foo', 'bar'] # the keys() method works! - ... assert list(s.values()) == [33, 65] # the values() method works! - ... assert list(s.items()) == [('foo', 33), ('bar', 65)] # these are the items - ... assert list(s.store.items()) == [('FOO', '!'), ('BAR', 'A')] # but note the internal representation - ... assert s.get('foo') == 33 # the get method works - ... assert s.get('no_such_key', 'something') == 'something' # return a default value - ... del(s['foo']) # you can delete an item given its key - ... assert len(s) == 1 # see, only one item left! - ... assert list(s.items()) == [('bar', 65)] # here it is - >>> - >>> # We can introduce this conversion layer in several ways. Here's a few... ###################### - >>> # by subclassing ############################################################################### - >>> class MyStore(Store): - ... def _id_of_key(self, k): - ... return k.upper() - ... def _key_of_id(self, _id): - ... return _id.lower() - ... def _data_of_obj(self, obj): - ... return chr(obj) - ... def _obj_of_data(self, data): - ... return ord(data) - >>> s = MyStore(store=dict()) # note that you don't need to specify dict(), since it's the default - >>> test_store(s) - >>> - >>> # by assigning functions to converters ########################################################## - >>> class MyStore(Store): - ... def __init__(self, store, _id_of_key, _key_of_id, _data_of_obj, _obj_of_data): - ... super().__init__(store) - ... self._id_of_key = _id_of_key - ... self._key_of_id = _key_of_id - ... self._data_of_obj = _data_of_obj - ... self._obj_of_data = _obj_of_data - ... - >>> s = MyStore(dict(), - ... _id_of_key=lambda k: k.upper(), - ... _key_of_id=lambda _id: _id.lower(), - ... _data_of_obj=lambda obj: chr(obj), - ... _obj_of_data=lambda data: ord(data)) - >>> test_store(s) - >>> - >>> # using a Mixin class ############################################################################# - >>> class Mixin: - ... def _id_of_key(self, k): - ... return k.upper() - ... def _key_of_id(self, _id): - ... return _id.lower() - ... def _data_of_obj(self, obj): - ... return chr(obj) - ... def _obj_of_data(self, data): - ... return ord(data) - ... - >>> class MyStore(Mixin, Store): # note that the Mixin must come before Store in the mro - ... pass - ... - >>> s = MyStore() # no dict()? No, because default anyway - >>> test_store(s) - >>> - >>> # adding wrapper methods to an already made Store instance ######################################### - >>> s = Store(dict()) - >>> s._id_of_key=lambda k: k.upper() - >>> s._key_of_id=lambda _id: _id.lower() - >>> s._data_of_obj=lambda obj: chr(obj) - >>> s._obj_of_data=lambda data: ord(data) - >>> test_store(s) - """ - - # __slots__ = ('_id_of_key', '_key_of_id', '_data_of_obj', '_obj_of_data') - - def __init__(self, store=dict): - # self._wrapped_methods = set(dir(Store)) - - if isinstance(store, type): - store = store() - self.store = store - - _id_of_key = static_identity_method - _key_of_id = static_identity_method - _data_of_obj = static_identity_method - _obj_of_data = static_identity_method - - _max_repr_size = None - - _errors_that_trigger_missing = (KeyError, FileNotFoundError) - - wrap = classmethod(cls_wrap) - - def __getattr__(self, attr): - """Delegate method to wrapped store if not part of wrapper store methods""" - return getattr(self.store, attr) - - def __hash__(self): - return self.store.__hash__() - - # Read #################################################################### - - def __getitem__(self, k: Key) -> Val: - # essentially: self._obj_of_data(self.store[self._id_of_key(k)]) - _id = self._id_of_key(k) - try: - data = self.store[_id] - except self._errors_that_trigger_missing: - return self.__missing__(k) - return self._obj_of_data(data) - - def __missing__(self, k): - raise KeyError(k) - -
[docs] def get(self, k: Key, default=None) -> Val: - if hasattr(self.store, "get"): # if store has a get method, use it - _id = self._id_of_key(k) - data = self.store.get(_id, no_such_item) - if data is not no_such_item: - return self._obj_of_data(data) - else: - return default - else: # if not, do the get function otherwise - if k in self: - return self._obj_of_data(self[k]) - else: - return default
- - # def update(self, other=(), /, **kwds): - # """ - # update(self, other=(), /, **kwds) - # D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. - # If E present and has a .keys() method, does: for k in E: D[k] = E[k] - # If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v - # In either case, this is followed by: for k, v in F.items(): D[k] = v - # :return: - # """ - - # Explore #################################################################### - def __iter__(self) -> KeyIter: - yield from (self._key_of_id(k) for k in self.store) - # return map(self._key_of_id, self.store.__iter__()) - - # def items(self) -> ItemIter: - # if hasattr(self.store, 'items'): - # yield from ((self._key_of_id(k), self._obj_of_data(v)) for k, v in self.store.items()) - # else: - # yield from ((self._key_of_id(k), self._obj_of_data(self.store[k])) for k in self.store.__iter__()) - - def __len__(self) -> int: - return len(self.store) - # return self.store.__len__() - - def __contains__(self, k) -> bool: - return self._id_of_key(k) in self.store - # return self.store.__contains__(self._id_of_key(k)) - - def head(self) -> Item: - k = None - try: - for k in self: - return k, self[k] - except Exception as e: - - from warnings import warn - - if k is None: - raise - else: - msg = ( - f"Couldn't get data for the key {k}. This could be be...\n" - ) - msg += "... because it's not a store (just a collection, that doesn't have a __getitem__)\n" - msg += ( - "... because there's a layer transforming outcoming keys that are not the ones the store actually " - "uses? If you didn't wrap the store with the inverse ingoing keys transformation, " - "that would happen.\n" - ) - msg += ( - "I'll ask the inner-layer what it's head is, but IT MAY NOT REFLECT the reality of your store " - "if you have some filtering, caching etc." - ) - msg += f"The error messages was: \n{e}" - warn(msg) - - for _id in self.store: - return self._key_of_id(_id), self._obj_of_data(self.store[_id]) - # NOTE: Old version didn't work when key mapping was asymmetrical - # for k, v in self.items(): - # return k, v - - # Write #################################################################### - def __setitem__(self, k: Key, v: Val): - return self.store.__setitem__(self._id_of_key(k), self._data_of_obj(v)) - - # def update(self, *args, **kwargs): - # return self.store.update(*args, **kwargs) - - # Delete #################################################################### - def __delitem__(self, k: Key): - return self.store.__delitem__(self._id_of_key(k)) - - # def clear(self): - # raise NotImplementedError(''' - # The clear method was overridden to make dangerous difficult. - # If you really want to delete all your data, you can do so by doing: - # try: - # while True: - # self.popitem() - # except KeyError: - # pass''') - - # Misc #################################################################### - def __repr__(self): - x = repr(self.store) - if isinstance(self._max_repr_size, int): - half = int(self._max_repr_size) - if len(x) > self._max_repr_size: - x = x[:half] + " ... " + x[-half:] - return x
- # return self.store.__repr__() - - -# Store.register(dict) # TODO: Would this be a good idea? To make isinstance({}, Store) be True (though missing head()) -KvStore = Store # alias with explict name - -######################################################################################################################## -# walking in trees - - -inf = float("infinity") - - -def val_is_mapping(p, k, v): - return isinstance(v, Mapping) - - -def asis(p, k, v): - return p, k, v - - -def tuple_keypath_and_val(p, k, v): - if p == (): # we're just begining (the root), - p = (k,) # so begin the path with the first key. - else: - p = (*p, k) # extend the path (append the new key) - return p, v - - -# TODO: More docs and doctests. This one even merits an extensive usage and example tutorial! -
[docs]def kv_walk( - v: Mapping, - yield_func=asis, # (p, k, v) -> what you want the gen to yield - walk_filt=val_is_mapping, # (p, k, v) -> whether to explore the nested structure v further - pkv_to_pv=tuple_keypath_and_val, - p=(), -): - """ - - :param v: - :param yield_func: (pp, k, vv) -> what ever you want the gen to yield - :param walk_filt: (p, k, vv) -> (bool) whether to explore the nested structure v further - :param pkv_to_pv: (p, k, v) -> (pp, vv) - where pp is a form of p + k (update of the path with the new node k) - and vv is the value that will be used by both walk_filt and yield_func - :param p: The path to v - - >>> d = {'a': 1, 'b': {'c': 2, 'd': 3}} - >>> list(kv_walk(d)) - [(('a',), 'a', 1), (('b', 'c'), 'c', 2), (('b', 'd'), 'd', 3)] - >>> list(kv_walk(d, lambda p, k, v: '.'.join(p))) - ['a', 'b.c', 'b.d'] - """ - # print(f"1: entered with: v={v}, p={p}") - for k, vv in v.items(): - # print(f"2: item: k={k}, vv={vv}") - pp, vv = pkv_to_pv( - p, k, vv - ) # update the path with k (and preprocess v if necessary) - if walk_filt( - p, k, vv - ): # should we recurse? (based on some function of p, k, v) - # print(f"3: recurse with: pp={pp}, vv={vv}\n") - yield from kv_walk( - vv, yield_func, walk_filt, pkv_to_pv, pp - ) # recurse - else: - # print(f"4: yield_func(pp={pp}, k={k}, vv={vv})\n --> {yield_func(pp, k, vv)}") - yield yield_func( - pp, k, vv - ) # yield something computed from p, k, vv
- - -
[docs]def has_kv_store_interface(o): - """Check if object has the KvStore interface (that is, has the kv wrapper methods - - Args: - o: object (class or instance) - - Returns: True if kv has the four key (in/out) and value (in/out) transformation methods - - """ - return ( - hasattr(o, "_id_of_key") - and hasattr(o, "_key_of_id") - and hasattr(o, "_data_of_obj") - and hasattr(o, "_obj_of_data") - )
- - -from abc import ABCMeta, abstractmethod -from py2store.errors import KeyValidationError - - -def _check_methods(C, *methods): - """ - Check that all methods listed are in the __dict__ of C, or in the classes of it's mro. - One trick pony borrowed from collections.abc. - """ - mro = C.__mro__ - for method in methods: - for B in mro: - if method in B.__dict__: - if B.__dict__[method] is None: - return NotImplemented - break - else: - return NotImplemented - return True - - -# Note: Not sure I want to do key validation this way. Perhaps better injected in _id_of_key? -
[docs]class KeyValidationABC(metaclass=ABCMeta): - """ - An ABC for an object writer. - Single purpose: store an object under a given key. - How the object is serialized and or physically stored should be defined in a concrete subclass. - """ - - __slots__ = () - - @abstractmethod - def is_valid_key(self, k): - pass - - def check_key_is_valid(self, k): - if not self.is_valid_key(k): - raise KeyValidationError("key is not valid: {}".format(k)) - - @classmethod - def __subclasshook__(cls, C): - if cls is KeyValidationABC: - return _check_methods(C, "is_valid_key", "check_key_is_valid") - return NotImplemented
- - -######################################################################################################################## -# Streams -from io import IOBase - - -# - -class stream_util: - def always_true(*args, **kwargs): - return True - - def do_nothing(*args, **kwargs): - pass - - def rewind(self, instance): - instance.seek(0) - - def skip_lines(self, instance, n_lines_to_skip=0): - instance.seek(0) - - -# TODO: What's the abstract class for Streams. IOBase doesn't seem to work? -# TODO: Two filters. Might just be able to use one, using sentinels - -
[docs]class Stream: - """A layer-able version of the stream interface - - __iter__ calls: _obj_of_data(map) - readlines calls: _obj_of_data(though iter) - readline calls: _obj_of_data - - - >>> from io import StringIO - >>> - >>> src = StringIO( - ... '''a, b, c - ... 1,2, 3 - ... 4, 5,6 - ... ''' - ... ) - >>> - >>> from py2store.base import Stream - >>> - >>> class MyStream(Stream): - ... def _obj_of_data(self, line): - ... return [x.strip() for x in line.strip().split(',')] - ... - >>> stream = MyStream(src) - >>> - >>> assert list(stream) == [['a', 'b', 'c'], ['1', '2', '3'], ['4', '5', '6']] - >>> assert stream.readlines() == [] # readlines should do the same as list(stream) - >>> stream.seek(0) # oh!... but we consumed the stream already, so let's go back to the beginning - 0 - >>> assert stream.readlines() == [['a', 'b', 'c'], ['1', '2', '3'], ['4', '5', '6']] # same as list(stream) - >>> stream.seek(0) # reverse again - 0 - >>> assert stream.readline() == ['a', 'b', 'c'] - >>> assert stream.readline() == ['1', '2', '3'] - - Let's add a filter! There's two kinds you can use. - One that is applied to the line before the data is transformed by _obj_of_data, - and the other that is applied after (to the obj). - - - >>> from py2store.base import Stream - >>> from io import StringIO - >>> - >>> src = StringIO( - ... '''a, b, c - ... 1,2, 3 - ... 4, 5,6 - ... ''') - >>> class MyFilteredStream(MyStream): - ... def _post_filt(self, obj): - ... return str.isnumeric(obj[0]) - >>> - >>> s = MyFilteredStream(src) - >>> - >>> assert list(s) == [['1', '2', '3'], ['4', '5', '6']] - >>> s.seek(0) - 0 - >>> assert s.readlines() == [['1', '2', '3'], ['4', '5', '6']] # same as list(stream) - >>> s.seek(0) - 0 - >>> assert s.readline() == ['1', '2', '3'] - - Recipes: - - _pre_iter: involving itertools.islice to skip header lines - - _pre_iter: involving enumerate to get line indices in stream iterator - - _pre_iter = functools.partial(map, line_pre_proc_func) to preprocess all lines with line_pre_proc_func - - _pre_iter: include filter before obj - """ - - def __init__(self, stream): - self.stream = stream - - wrap = classmethod(cls_wrap) - - # _data_of_obj = static_identity_method # for write methods - _pre_iter = static_identity_method - _obj_of_data = static_identity_method - _post_filt = stream_util.always_true - - def __iter__(self): - for line in self._pre_iter(self.stream): - obj = self._obj_of_data(line) - if self._post_filt(obj): - yield obj - - # TODO: See pros and cons of above vs below: - # yield from filter(self._post_filt, - # map(self._obj_of_data, - # self._pre_iter(self.stream))) - - # _wrapped_methods = {'__iter__'} - - def __getattr__(self, attr): - """Delegate method to wrapped store if not part of wrapper store methods""" - return getattr(self.stream, attr) - # if attr in self._wrapped_methods: - # return getattr(self, attr) - # else: - # return getattr(self.stream, attr) - - def __enter__(self): - self.stream.__enter__() - return self - # return self._pre_proc(self.stream) # moved to iter to - - def __exit__(self, exc_type, exc_val, exc_tb): - return self.stream.__exit__(exc_type, exc_val, exc_tb) # TODO: Should we have a _post_proc? Uses?
- - # def readlines(self): - # # TODO: Should we use self.stream.readlines() instead? Is readlines expected to be aligned with __iter__? - # return list(self) - # - # def readline(self): - # # TODO: Should we use self.stream.readline() instead? Is readline expected to be aligned with __iter__? - # return next(iter(self)) - - # def read(self): - # yield from (self._obj_of_data(k) for k in self.stream) -######################################################################################################################## -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/caching.html b/docs/_modules/py2store/caching.html deleted file mode 100644 index 3367d49..0000000 --- a/docs/_modules/py2store/caching.html +++ /dev/null @@ -1,821 +0,0 @@ - - - - - - - - py2store.caching — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.caching

-from functools import wraps, partial
-from typing import Iterable, Union, Callable, Hashable, Any
-
-from py2store.trans import store_decorator
-
-
-########################################################################################################################
-# Read caching
-
-# The following is a "Cache-Aside" read-cache with NO builtin cache update or refresh mechanism.
-def mk_memoizer(cache):
-    def memoize(method):
-        @wraps(method)
-        def memoizer(self, k):
-            if k not in cache:
-                val = method(self, k)
-                cache[k] = val  # cache it
-                return val
-            else:
-                return cache[k]
-
-        return memoizer
-
-    return memoize
-
-
-def _mk_cache_instance(cache=None, assert_attrs=()):
-    """Make a cache store (if it's not already) from a type or a callable, or just return dict.
-    Also assert the presence of given attributes
-
-    >>> _mk_cache_instance(dict(a=1, b=2))
-    {'a': 1, 'b': 2}
-    >>> _mk_cache_instance(None)
-    {}
-    >>> _mk_cache_instance(dict)
-    {}
-    >>> _mk_cache_instance(list, ('__getitem__', '__setitem__'))
-    []
-    >>> _mk_cache_instance(tuple, ('__getitem__', '__setitem__'))
-    Traceback (most recent call last):
-        ...
-    AssertionError: cache should have the __setitem__ method, but does not: ()
-
-    """
-    if isinstance(assert_attrs, str):
-        assert_attrs = (assert_attrs,)
-    if cache is None:
-        cache = {}  # use a dict (memory caching) by default
-    elif (isinstance(cache, type)  # if caching_store is a type...
-          or (not hasattr(cache, '__getitem__')  # ... or is a callable without a __getitem__
-              and callable(cache))):
-        cache = cache()  # ... assume it's a no-argument callable that makes the instance
-    for method in assert_attrs or ():
-        assert hasattr(cache, method), f"cache should have the {method} method, but does not: {cache}"
-    return cache
-
-
-# TODO: Make it so that the resulting store gets arguments to construct it's own cache
-#   right now, only cache instances or no-argument cache types can be used.
-#
-
-
[docs]@store_decorator -def mk_cached_store( - store=None, - *, - cache=dict -): - """ - - Args: - store: The class of the store you want to cache - cache: The store you want to use to cache. Anything with a __setitem__(k, v) and a __getitem__(k). - By default, it will use a dict - - Returns: A subclass of the input store, but with caching (to the cache store) - - >>> from py2store.caching import mk_cached_store - >>> import time - >>> class SlowDict(dict): - ... sleep_s = 0.2 - ... def __getitem__(self, k): - ... time.sleep(self.sleep_s) - ... return super().__getitem__(k) - ... - ... - >>> d = SlowDict({'a': 1, 'b': 2, 'c': 3}) - >>> - >>> d['a'] # Wow! Takes a long time to get 'a' - 1 - >>> cache = dict() - >>> CachedSlowDict = mk_cached_store(store=SlowDict, cache=cache) - >>> - >>> s = CachedSlowDict({'a': 1, 'b': 2, 'c': 3}) - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c'] - cache: [] - >>> # This will take a LONG time because it's the first time we ask for 'a' - >>> v = s['a'] - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c'] - cache: ['a'] - >>> # This will take very little time because we have 'a' in the cache - >>> v = s['a'] - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c'] - cache: ['a'] - >>> # But we don't have 'b' - >>> v = s['b'] - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c'] - cache: ['a', 'b'] - >>> # But now we have 'b' - >>> v = s['b'] - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c'] - cache: ['a', 'b'] - >>> s['d'] = 4 # and we can do things normally (like put stuff in the store) - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c', 'd'] - cache: ['a', 'b'] - >>> s['d'] # if we ask for it again though, it will take time (the first time) - 4 - >>> print(f"store: {list(s)}\\ncache: {list(cache)}") - store: ['a', 'b', 'c', 'd'] - cache: ['a', 'b', 'd'] - >>> # Of course, we could write 'd' in the cache as well, to get it quicker, - >>> # but that's another story: The story of write caches! - >>> - >>> # And by the way, your "cache wrapped" store hold a pointer to the cache it's using, - >>> # so you can take a peep there if needed: - >>> s._cache - {'a': 1, 'b': 2, 'd': 4} - """ - - cache = _mk_cache_instance(cache, assert_attrs=('__getitem__', '__setitem__')) - assert isinstance(store, type), f"store should be a type, was a {type(store)}: {store}" - - class CachedStore(store): - _cache = cache - - @mk_memoizer(cache) - def __getitem__(self, k): - return super().__getitem__(k) - - return CachedStore
- - -
[docs]@store_decorator -def mk_sourced_store( - store=None, - *, - source=None, - return_source_data=True -): - """ - - Args: - store: The class of the store you want to cache - cache: The store you want to use to cache. Anything with a __setitem__(k, v) and a __getitem__(k). - By default, it will use a dict - return_source_data: - Returns: A subclass of the input store, but with caching (to the cache store) - - - :param store: The class of the store you're talking to. This store acts as the cache - :param source: The store that is used to populate the store (cache) when a key is missing there. - :param return_source_data: - If True, will return ``source[k]`` as is. This should be used only if ``store[k]`` would return the same. - If False, will first write to cache (``store[k] = source[k]``) then return ``store[k]``. - The latter introduces a performance hit (we write and then read again from the cache), - but ensures consistency (and is useful if the writing or the reading to/from store - transforms the data in some way. - :return: A decorated store - - Here are two stores pretending to be local and remote data stores respectively. - - >>> from py2store.caching import mk_sourced_store - >>> - >>> class Local(dict): - ... def __getitem__(self, k): - ... print(f"looking for {k} in Local") - ... return super().__getitem__(k) - >>> - >>> class Remote(dict): - ... def __getitem__(self, k): - ... print(f"looking for {k} in Remote") - ... return super().__getitem__(k) - - - Let's make a remote store with two elements in it, and a local store class that asks the remote store for stuff - if it can't find it locally. - - >>> remote = Remote({'foo': 'bar', 'hello': 'world'}) - >>> SourcedLocal = mk_sourced_store(Local, source=remote) - >>> s = SourcedLocal({'some': 'local stuff'}) - >>> list(s) # the local store has one key - ['some'] - - # but if we ask for a key that is in the remote store, it provides it - >>> assert s['foo'] == 'bar' - looking for foo in Local - looking for foo in Remote - - >>> list(s) - ['some', 'foo'] - - See that next time we ask for the 'foo' key, the local store provides it: - - >>> assert s['foo'] == 'bar' - looking for foo in Local - - >>> assert s['hello'] == 'world' - looking for hello in Local - looking for hello in Remote - >>> list(s) - ['some', 'foo', 'hello'] - - We can still add stuff (locally)... - - >>> s['something'] = 'else' - >>> list(s) - ['some', 'foo', 'hello', 'something'] - """ - assert source is not None, "You need to specify a source" - - source = _mk_cache_instance(source, assert_attrs=('__getitem__',)) - - assert isinstance(store, type), f"store should be a type, was a {type(store)}: {store}" - - if return_source_data: - class SourcedStore(store): - _src = source - - def __missing__(self, k): - # if you didn't have it "locally", ask src for it - v = self._src[k] # ... get it from _src, - self[k] = v # ... store it in self - return v # ... and return it. - - else: - class SourcedStore(store): - _src = source - - def __missing__(self, k): - # if you didn't have it "locally", ask src for it - v = self._src[k] # ... get it from _src, - self[k] = v # ... store it in self - return self[k] # retrieve it again and return - - return SourcedStore
- - -# cache = _mk_cache_instance(cache, assert_attrs=('__getitem__',)) -# assert isinstance(store, type), f"store should be a type, was a {type(store)}: {store}" -# -# class CachedStore(store): -# _cache = cache -# -# @mk_memoizer(cache) -# def __getitem__(self, k): -# return super().__getitem__(k) -# -# return CachedStore - - -# TODO: Didn't finish this. Finish, doctest, and remove underscore -def _pre_condition_containment(store=None, *, bool_key_func): - """Adds a custom boolean key function `bool_key_func` before the store_cls.__contains__ check is performed. - - It is meant to be used to create smart read caches. - - This can be used, for example, to perform TTL caching by having `bool_key_func` check on how long - ago a cache item has been created, and returning False if the item is past it's expiry time. - """ - - class PreContaimentStore(store): - def __contains__(self, k): - return bool_key_func(k) and super().__contains__(k) - - return PreContaimentStore - - -def _slow_but_somewhat_general_hash(*args, **kwargs): - """ - Attempts to create a hash of the inputs, recursively resolving the most common hurdles (dicts, sets, lists) - Returns: A hash value for the input - - >>> _slow_but_somewhat_general_hash(1, [1, 2], a_set={1,2}, a_dict={'a': 1, 'b': [1,2]}) - ((1, (1, 2)), (('a_set', (1, 2)), ('a_dict', (('a', 1), ('b', (1, 2)))))) - """ - if len(kwargs) == 0 and len(args) == 1: - single_val = args[0] - if hasattr(single_val, "items"): - return tuple( - (k, _slow_but_somewhat_general_hash(v)) - for k, v in single_val.items() - ) - elif isinstance(single_val, (set, list)): - return tuple(single_val) - else: - return single_val - else: - return ( - tuple(_slow_but_somewhat_general_hash(x) for x in args), - tuple( - (k, _slow_but_somewhat_general_hash(v)) - for k, v in kwargs.items() - ), - ) - - -# TODO: Could add an empty_cache function attribute. -# Wrap the store cache to track new keys, and delete those (and only those!!) when emptying the store. -
[docs]def store_cached(store, key_func: Callable): - """ - Function output memorizer but using a specific (usually persisting) store as it's memory and a key_func to - compute the key under which to store the output. - - The key can be - - a single value under which the output should be stored, regardless of the input. - - a key function that is called on the inputs to create a hash under which the function's output should be stored. - - Args: - store: The key-value store to use for caching. Must support __getitem__ and __setitem__. - key_func: The key function that is called on the input of the function to create the key value. - - Note: Union[Callable, Any] is equivalent to just Any, but reveals the two cases of a key more clearly. - Note: No, Union[Callable, Hashable] is not better. For one, general store keys are not restricted to hashable keys. - Note: No, they shouldn't. - - See Also: store_cached_with_single_key (for a version where the cache store key doesn't depend on function's args) - - >>> # Note: Our doc test will use dict as the store, but to make the functionality useful beyond existing - >>> # RAM-memorizer, you should use actual "persisting" stores that store in local files, or DBs, etc. - >>> store = dict() - >>> @store_cached(store, lambda *args: args) - ... def my_data(x, y): - ... print("Pretend this is a long computation") - ... return x + y - >>> t = my_data(1, 2) # note the print below (because the function is called - Pretend this is a long computation - >>> tt = my_data(1, 2) # note there's no print (because the function is NOT called) - >>> assert t == tt - >>> tt - 3 - >>> my_data(3, 4) # but different inputs will trigger the actual function again - Pretend this is a long computation - 7 - >>> my_data._cache - {(1, 2): 3, (3, 4): 7} - """ - assert callable(key_func), ( - "key_func should be a callable: " - "It's called on the wrapped function's input to make a key for the caching store." - ) - - def func_wrapper(func): - @wraps(func) - def wrapped_func(*args, **kwargs): - key = key_func(*args, **kwargs) - if key in store: # if the store has that key... - return store[ - key - ] # ... just return the data cached under this key - else: # if the store doesn't have it... - output = func( - *args, **kwargs - ) # ... call the function and get the output - store[key] = output # store the output under the key - return output - - wrapped_func._cache = store - return wrapped_func - - return func_wrapper
- - -
[docs]def store_cached_with_single_key(store, key): - """ - Function output memorizer but using a specific store and key as it's memory. - - Use in situations where you have a argument-less function or bound method that computes some data whose dependencies - are static enough that there's enough advantage to make the data refresh explicit (by deleting the cache entry) - instead of making it implicit (recomputing/refetching the data every time). - - The key should be a single value under which the output should be stored, regardless of the input. - - Note: The wrapped function comes with a empty_cache attribute, which when called, empties the cache (i.e. removes - the key from the store) - - Note: The wrapped function has a hidden `_cache` attribute pointing to the store in case you need to peep into it. - - Args: - store: The cache. The key-value store to use for caching. Must support __getitem__ and __setitem__. - key: The store key under which to store the output of the function. - - Note: Union[Callable, Any] is equivalent to just Any, but reveals the two cases of a key more clearly. - Note: No, Union[Callable, Hashable] is not better. For one, general store keys are not restricted to hashable keys. - Note: No, they shouldn't. - - See Also: store_cached (for a version whose keys are computed from the wrapped function's input. - - >>> # Note: Our doc test will use dict as the store, but to make the functionality useful beyond existing - >>> # RAM-memorizer, you should use actual "persisting" stores that store in local files, or DBs, etc. - >>> store = dict() - >>> @store_cached_with_single_key(store, 'whatevs') - ... def my_data(): - ... print("Pretend this is a long computation") - ... return [1, 2, 3] - >>> t = my_data() # note the print below (because the function is called - Pretend this is a long computation - >>> tt = my_data() # note there's no print (because the function is NOT called) - >>> assert t == tt - >>> tt - [1, 2, 3] - >>> my_data._cache # peep in the cache - {'whatevs': [1, 2, 3]} - >>> # let's empty the cache - >>> my_data.empty_cache_entry() - >>> assert 'whatevs' not in my_data._cache # see that the cache entry is gone. - >>> t = my_data() # so when you call the function again, it prints again!d - Pretend this is a long computation - """ - - def func_wrapper(func): - # TODO: Enforce that the func is argument-less or a bound method here? - - # TODO: WhyTF doesn't this work: (unresolved reference) - # if key is None: - # key = '.'.join([func.__module__, func.__qualname___]) - - @wraps(func) - def wrapped_func(*args, **kwargs): - if key in store: # if the store has that key... - return store[ - key - ] # ... just return the data cached under this key - else: - output = func(*args, **kwargs) - store[key] = output - return output - - wrapped_func._cache = store - wrapped_func.empty_cache_entry = lambda: wrapped_func._cache.__delitem__( - key - ) - return wrapped_func - - return func_wrapper
- - -def ensure_clear_to_kv_store(store): - if not hasattr(store, "clear"): - - def _clear(kv_store): - for k in kv_store: - del kv_store[k] - - store.clear = _clear - return store - - -def flush_on_exit(cls): - new_cls = type(cls.__name__, (cls,), {}) - - if not hasattr(new_cls, "__enter__"): - def __enter__(self): - return self - - new_cls.__enter__ = __enter__ - - if not hasattr(new_cls, "__exit__"): - - def __exit__(self, *args, **kwargs): - return self.flush_cache() - - else: # TODO: Untested case where the class already has an __exit__, which we want to call after flush - - @wraps(new_cls.__exit__) - def __exit__(self, *args, **kwargs): - self.flush_cache() - return super().__exit__(*args, **kwargs) - - new_cls.__exit__ = __exit__ - - return new_cls - - -
[docs]def mk_write_cached_store( - store, - *, - w_cache=dict, - flush_cache_condition=None -): - """Wrap a write cache around a store. - - Args: - w_cache: The store to (write) cache to - flush_cache_condition: The condition to apply to the cache - to decide whether it's contents should be flushed or not - - A ``w_cache`` must have a clear method (that clears the cache's contents). - If you know what you're doing and want to add one to your input kv store, - you can do so by calling ``ensure_clear_to_kv_store(store)`` - -- this will add a ``clear`` method inplace AND return the resulting store as well. - - We didn't add this automatically because the first thing ``mk_write_cached_store`` will do is call clear, - to remove all the contents of the store. - You don't want to do this unwittingly and delete a bunch of precious data!! - - >>> from py2store.caching import mk_write_cached_store, ensure_clear_to_kv_store - >>> from py2store import Store - >>> - >>> def print_state(store): - ... print(f"store: {store} ----- store._w_cache: {store._w_cache}") - ... - >>> class MyStore(dict): ... - >>> MyCachedStore = mk_write_cached_store(MyStore, w_cache={}) # wrap MyStore with a (dict) write cache - >>> s = MyCachedStore() # make a MyCachedStore instance - >>> print_state(s) # print the contents (both store and cache), see that it's empty - store: {} ----- store._w_cache: {} - >>> s['hello'] = 'world' # write 'world' in 'hello' - >>> print_state(s) # see that it hasn't been written - store: {} ----- store._w_cache: {'hello': 'world'} - >>> s['ding'] = 'dong' - >>> print_state(s) - store: {} ----- store._w_cache: {'hello': 'world', 'ding': 'dong'} - >>> s.flush_cache() # manually flush the cache - >>> print_state(s) # note that store._w_cache is empty, but store has the data now - store: {'hello': 'world', 'ding': 'dong'} ----- store._w_cache: {} - >>> - >>> # But you usually want to use the store as a context manager - >>> MyCachedStore = mk_write_cached_store( - ... MyStore, w_cache={}, - ... flush_cache_condition=None) - >>> - >>> the_persistent_dict = dict() - >>> - >>> s = MyCachedStore(the_persistent_dict) - >>> with s: - ... print("===> Before writing data:") - ... print_state(s) - ... s['hello'] = 'world' - ... print("===> Before exiting the with block:") - ... print_state(s) - ... - ===> Before writing data: - store: {} ----- store._w_cache: {} - ===> Before exiting the with block: - store: {} ----- store._w_cache: {'hello': 'world'} - >>> - >>> print("===> After exiting the with block:"); print_state(s) # Note that the cache store flushed! - ===> After exiting the with block: - store: {'hello': 'world'} ----- store._w_cache: {} - >>> - >>> # Example of auto-flushing when there's at least two elements - >>> class MyStore(dict): ... - ... - >>> MyCachedStore = mk_write_cached_store( - ... MyStore, w_cache={}, - ... flush_cache_condition=lambda w_cache: len(w_cache) >= 3) - >>> - >>> s = MyCachedStore() - >>> with s: - ... for i in range(7): - ... s[i] = i * 10 - ... print_state(s) - ... - store: {} ----- store._w_cache: {0: 0} - store: {} ----- store._w_cache: {0: 0, 1: 10} - store: {0: 0, 1: 10, 2: 20} ----- store._w_cache: {} - store: {0: 0, 1: 10, 2: 20} ----- store._w_cache: {3: 30} - store: {0: 0, 1: 10, 2: 20} ----- store._w_cache: {3: 30, 4: 40} - store: {0: 0, 1: 10, 2: 20, 3: 30, 4: 40, 5: 50} ----- store._w_cache: {} - store: {0: 0, 1: 10, 2: 20, 3: 30, 4: 40, 5: 50} ----- store._w_cache: {6: 60} - >>> # There was still something left in the cache before exiting the with block. But now... - >>> print_state(s) - store: {0: 0, 1: 10, 2: 20, 3: 30, 4: 40, 5: 50, 6: 60} ----- store._w_cache: {} - """ - - w_cache = _mk_cache_instance(w_cache, ('clear', '__setitem__', 'items')) - - w_cache.clear() # assure the cache is empty, by emptying it. - - @flush_on_exit - class WriteCachedStore(store): - _w_cache = w_cache - _flush_cache_condition = staticmethod(flush_cache_condition) - - if flush_cache_condition is None: - - def __setitem__(self, k, v): - return self._w_cache.__setitem__(k, v) - - else: - assert callable(flush_cache_condition), ( - "flush_cache_condition must be None or a callable ", - "taking the (write) cache store as an input and returning" - "True if and only if the cache should be flushed.", - ) - - def __setitem__(self, k, v): - r = self._w_cache.__setitem__(k, v) - if self._flush_cache_condition(self._w_cache): - self.flush_cache() - return r - - if not hasattr(store, "flush"): - - def flush(self, items: Iterable = tuple()): - for k, v in items: - super().__setitem__(k, v) - - def flush_cache(self): - self.flush(self._w_cache.items()) - return self._w_cache.clear() - - return WriteCachedStore
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/core.html b/docs/_modules/py2store/core.html deleted file mode 100644 index f2918c8..0000000 --- a/docs/_modules/py2store/core.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - py2store.core — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.core

-import os
-
-from py2store.key_mappers.paths import PrefixRelativizationMixin
-
-file_sep = os.path.sep
-
-
-def ensure_slash_suffix(path):
-    if not path.endswith(file_sep):
-        return path + file_sep
-    else:
-        return path
-
-
-
[docs]class PrefixRelativization(PrefixRelativizationMixin): - """A key wrap that allows one to interface with absolute paths through relative paths. - The original intent was for local files. Instead of referencing files through an absolute path such as - /A/VERY/LONG/ROOT/FOLDER/the/file/we.want - we can instead reference the file as - the/file/we.want - - But PrefixRelativization can be used, not only for local paths, but when ever a string reference is involved. - In fact, not only strings, but any key object that has a __len__, __add__, and subscripting. - """ - - def __init__(self, _prefix=""): - self._prefix = _prefix
- -# class PathFormat: # is now in py2store.persisters.local_files -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/errors.html b/docs/_modules/py2store/errors.html deleted file mode 100644 index be8a0c9..0000000 --- a/docs/_modules/py2store/errors.html +++ /dev/null @@ -1,351 +0,0 @@ - - - - - - - - py2store.errors — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.errors

-from collections.abc import Mapping
-from inspect import signature
-
-
-# TODO: More on wrapped_callback: Handle *args too. Make it work with builtins (no signature!)
-# TODO: What about traceback?
-# TODO: Make it a more general and useful store decorator. Trans store into an getitem exception catching store.
-
[docs]def items_with_caught_exceptions( - d: Mapping, - callback=None, - catch_exceptions=(Exception,), - yield_callback_output=False, -): - """ - Do what Mapping.items() does, but catching exceptions when getting the values for a key. - - - Some time your `store.items()` is annoying because of some exceptions that happen - when you're retrieving some value for some of the keys. - - Yes, if that happens, it's that something is wrong with your store, and yes, - if it's a store that's going to be used a lot, you really should build the right store - that doesn't have that problem. - - But now that we appeased the paranoid naysayers with that warning, let's get to business: - Sometimes you just want to get through the hurdle to get the job done. Sometimes your store is good enough, - except for a few exceptions. Sometimes your store gets it's keys from a large pool of possible keys - (e.g. github stores or kaggle stores, or any store created by a free-form search seed), - so you can't really depend on the fact that all the keys given by your key iterator - will give you a value without exception - -- especially if you slapped on a bunch of post-processing on the out-coming values. - - So you can right a for loop to iterate over your keys, catch the exceptions, do something with it... - - Or, in many cases, you can just use `items_with_caught_exceptions`. - - :param d: Any Mapping - :param catch_exceptions: A tuple of exceptions that should be caught - :param callback: A function that will be called every time an exception is caught. - The signature of the callback function is required to be: - k (key), e (error obj), d (mapping), i (index) - but - :return: An (key, val) generator with exceptions caught - - >>> from collections.abc import Mapping - >>> class Test(Mapping): # a Mapping class that has keys 0..9, but raises of KeyError if the key is not even - ... n = 10 - ... def __iter__(self): - ... yield from range(2, self.n) - ... def __len__(self): - ... return self.n - ... def __getitem__(self, k): - ... if k % 2 == 0: - ... return k - ... else: - ... raise KeyError('Not even') - >>> - >>> list(items_with_caught_exceptions(Test())) - [(2, 2), (4, 4), (6, 6), (8, 8)] - >>> - >>> def my_log(k, e): - ... print(k, e) - >>> list(items_with_caught_exceptions(Test(), callback=my_log)) - 3 'Not even' - 5 'Not even' - 7 'Not even' - 9 'Not even' - [(2, 2), (4, 4), (6, 6), (8, 8)] - >>> def my_other_log(i): - ... print(i) - >>> list(items_with_caught_exceptions(Test(), callback=my_other_log)) - 1 - 3 - 5 - 7 - [(2, 2), (4, 4), (6, 6), (8, 8)] - """ - - # wrap the input callback to make the callback definition less constrained for the user. - if callback is not None: - try: - params = signature(callback).parameters - - def wrapped_callback(**kwargs): - kwargs = {k: v for k, v in kwargs.items() if k in params} - return callback(**kwargs) - - except ValueError: - - def wrapped_callback(k, e, d, i): - return callback(k, e, d, i) - - else: - - def wrapped_callback(k, e, d, i): - pass # do nothing - - for i, k in enumerate(d): # iterate over keys of the mapping - try: - v = d[k] # try getting the value... - yield k, v # and if you do, yield the (k, v) pair - except catch_exceptions as e: # catch the specific exceptions you requested to catch - t = wrapped_callback(k=k, e=e, d=d, i=i) # call it - if ( - yield_callback_output - ): # if the user wants the output of the callback - yield t # yield it
- - -def _assert_condition(condition, err_msg="", err_cls=AssertionError): - if not condition: - raise err_cls(err_msg) - - -
[docs]class KeyValidationError(ValueError): - """Error to raise when a key is not valid""" - - pass
- - -
[docs]class NoSuchKeyError(KeyError): - pass
- - -
[docs]class OperationNotAllowed(NotImplementedError): - pass
- - -
[docs]class ReadsNotAllowed(OperationNotAllowed): - pass
- - -
[docs]class WritesNotAllowed(OperationNotAllowed): - pass
- - -
[docs]class DeletionsNotAllowed(OperationNotAllowed): - pass
- - -
[docs]class IterationNotAllowed(OperationNotAllowed): - pass
- - -
[docs]class OverWritesNotAllowedError(OperationNotAllowed): - """Error to raise when a key is not valid""" - - pass
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/examples/code_navig.html b/docs/_modules/py2store/examples/code_navig.html deleted file mode 100644 index 8cf8c1a..0000000 --- a/docs/_modules/py2store/examples/code_navig.html +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - - py2store.examples.code_navig — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.examples.code_navig

-"""
-Get stats about packages. Your own, or other's.
-Things like...
-
->>> import collections
->>> modules_info_df(collections)
-                      lines  empty_lines  ...  num_of_functions  num_of_classes
-collections.__init__   1273          189  ...                 1               9
-collections.abc           3            1  ...                 0              25
-<BLANKLINE>
-[2 rows x 7 columns]
->>> modules_info_df_stats(collections.abc)
-lines                      1276.000000
-empty_lines                 190.000000
-comment_lines                73.000000
-docs_lines                  133.000000
-function_lines              138.000000
-num_of_functions              1.000000
-num_of_classes               34.000000
-empty_lines_ratio             0.148903
-comment_lines_ratio           0.057210
-function_lines_ratio          0.108150
-mean_lines_per_function     138.000000
-dtype: float64
->>> stats_of(['urllib', 'json', 'collections'])
-                              urllib         json  collections
-empty_lines_ratio           0.157034     0.136818     0.148903
-comment_lines_ratio         0.074142     0.038432     0.057210
-function_lines_ratio        0.213907     0.449654     0.108150
-mean_lines_per_function    13.463768    41.785714   138.000000
-lines                    4343.000000  1301.000000  1276.000000
-empty_lines               682.000000   178.000000   190.000000
-comment_lines             322.000000    50.000000    73.000000
-docs_lines                425.000000   218.000000   133.000000
-function_lines            929.000000   585.000000   138.000000
-num_of_functions           69.000000    14.000000     1.000000
-num_of_classes             55.000000     3.000000    34.000000
-"""
-import re
-import os
-from functools import wraps
-
-from py2store import filt_iter
-from py2store.sources import Attrs
-from py2store.filesys import FileStringReader, RelPathFileStringReader
-from types import FunctionType, ModuleType
-from inspect import getsource
-
-psep = os.path.sep
-
-DFLT_ON_ERROR = "ignore"  # could be 'print', 'ignore', or 'raise'
-
-empty_line = re.compile("^\s*$")
-comment_line_p = re.compile("^\s+#.+$")
-line_p = re.compile("\n|\r|\n\r|\r\n")
-only_py_ext = lambda path: path.endswith(".py")
-no_test_folder = lambda path: "test" not in path.split(psep)
-
-
-def _root_dir_and_name(root):
-    """
-    The parent path and leaf name for the given root
-    :param root: module instance, dot path, or directory path
-    :return:
-    """
-    if isinstance(root, str) and not os.path.dirname(root):
-        root = __import__(
-            root
-        )  # assume it's a dot path string of a module and try to import it
-    if isinstance(root, ModuleType):
-        root = os.path.dirname(root.__file__)
-    if root.endswith(psep):
-        root = root[:-1]
-    return os.path.dirname(root), os.path.basename(root)
-
-
-def mk_pycode_obj(root, filepath_filt=only_py_ext):
-    if isinstance(filepath_filt, str):
-        filt_pattern = re.compile(filepath_filt)
-        filepath_filt = filt_pattern.match
-    parent_dir, dirname = _root_dir_and_name(root)
-    root = os.path.join(parent_dir, dirname)
-
-    @filt_iter(filt=filepath_filt)
-    class PyCodeReader(FileStringReader):
-        pass
-
-    pycode = PyCodeReader(root)
-    return pycode, root
-
-
-# @filt_iter(only_py_ext, name='PyCodeReader')
-
[docs]class PyCodeReader(RelPathFileStringReader): - @wraps(FileStringReader.__init__) - def __init__(self, rootdir, *args, **kwargs): - parent_dir, dirname = _root_dir_and_name(rootdir) - rootdir = os.path.join(parent_dir, dirname) - super().__init__(rootdir, *args, **kwargs)
- - -
[docs]def mk_code_navigator(root, filepath_filt=only_py_ext, on_error=DFLT_ON_ERROR): - """ - Yields statistics (as dicts) of modules under the root module or directory of a python package. - - :param root: module instance, dot path, or directory path - :param filepath_filt: filepath filter function or regular expression - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', 'raise', or 'yield' - :return: A generator of dicts - """ - """Gives us statistics given the root module or directory of a python package""" - pycode, root = mk_pycode_obj(root, filepath_filt) - - for k, code_str in pycode.items(): - try: - yield extract_info(root, k, code_str) - except Exception as e: - if on_error == "print": - print(f"Problem with {k}: {str(e)[:50]}\n") - elif on_error == "ignore": - pass - elif on_error == "yield": - yield {"filepath": k, "error": e} - else: - raise
- - -def extract_info(root, k, code_str): - objs = get_objs(root, k) - return { - "filepath": k, - "lines": len(lines(code_str)), - "empty_lines": sum( - bool(empty_line.match(line)) for line in lines(code_str) - ), - "comment_lines": sum( - bool(comment_line_p.match(line)) - for line in lines(code_str) - ), - "docs_lines": sum( - len(lines(obj.__doc__ or "")) for obj in objs - ), - "function_lines": sum( - _num_lines_of_function_code(obj) - for obj in objs - if isinstance(obj, FunctionType) - ), - "num_of_functions": sum( - isinstance(obj, FunctionType) for obj in objs - ), - "num_of_classes": sum(isinstance(obj, type) for obj in objs), - } - - -def lines(string): - return line_p.split(string) - - -def _num_lines_of_function_code(obj: FunctionType): - assert isinstance(obj, FunctionType) - return len(getsource(obj).split("\n")) - len( - (obj.__doc__ or "").split("\n") - ) - - -# TODO: Must be a standard lib for this! -def _path_to_module_str(path, root_path): - """ - The dot-path module string for a path (given the root_path, assumed to be on the python path) - :param path: The path to the module. - :param root_path: The path that's assumed to be on the python path - :return: - """ - assert path.endswith(".py") - path = path[:-3] - - if root_path.endswith(psep): - root_path = root_path[:-1] - root_path, root_package = _root_dir_and_name(root_path) - len_root = len(root_path) + 1 - path_parts = path[len_root:].split(psep) - if path_parts[-1] == "__init__.py": - path_parts = path_parts[:-1] - return ".".join(path_parts) - - -def get_objs(root, k): - name = _path_to_module_str(k, root) - module_store = Attrs.module_from_path( - k, key_filt=lambda x: not x.startswith("__"), name=name - ) - - def obj_filt( - obj, - ): # to make sure we only analyze objects defined in module itself, not imported - obj_module = getattr(obj, "__module__", None) - if obj_module: - return obj_module == name - - objs = list( - filter(obj_filt, (vv._source for vv in module_store.values())) - ) - return objs - - -
[docs]def modules_info_df( - root, filepath_filt=only_py_ext, index_field=None, on_error=DFLT_ON_ERROR -): - """ - A pandas DataFrame of stats of the root (package or directory thereof). - :param root: module or directory path - :param filepath_filt: filepath filter function or regular expression - :param index_field: function or field string that should be used for the indexing of modules - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', or 'raise' - :return: A DataFrame whose rows contain information for each module - - >>> import urllib - >>> modules_info_df(urllib) - lines empty_lines ... num_of_functions num_of_classes - urllib.error 78 18 ... 0 3 - urllib.request 2743 404 ... 23 28 - urllib.__init__ 1 1 ... 0 0 - urllib.response 81 24 ... 0 4 - urllib.robotparser 274 36 ... 0 4 - urllib.parse 1166 199 ... 46 16 - <BLANKLINE> - [6 rows x 7 columns] - """ - - import pandas as pd - - # mk_code_navigator was modules_info_gen here: - d = list(mk_code_navigator(root, filepath_filt, on_error=on_error)) - - if index_field is None: - dirpath, dirname = _root_dir_and_name(root) - root = os.path.join(dirpath, dirname) - index_field = lambda x: _path_to_module_str(x.pop("filepath"), root) - - if isinstance(index_field, str): - return pd.DataFrame(d).set_index(index_field) - elif callable(index_field): - index = [index_field(x) for x in d] - return pd.DataFrame(index=index, data=d)
- - -
[docs]def modules_info_df_stats( - root, filepath_filt=only_py_ext, index_field=None, on_error=DFLT_ON_ERROR -): - """ - A pandas Series of statistics over all modules of some root (package or directory thereof). - :param root: module or directory path - :param filepath_filt: filepath filter function or regular expression - :param index_field: function or field string that should be used for the indexing of modules - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', or 'raise' - :return: A Series whose rows containing statistics - - >>> import json - >>> modules_info_df_stats(json) - lines 1301.000000 - empty_lines 178.000000 - comment_lines 50.000000 - docs_lines 218.000000 - function_lines 585.000000 - num_of_functions 14.000000 - num_of_classes 3.000000 - empty_lines_ratio 0.136818 - comment_lines_ratio 0.038432 - function_lines_ratio 0.449654 - mean_lines_per_function 41.785714 - dtype: float64 - >>> modules_info_df_stats('collections.abc') - lines 1276.000000 - empty_lines 190.000000 - comment_lines 73.000000 - docs_lines 133.000000 - function_lines 138.000000 - num_of_functions 1.000000 - num_of_classes 34.000000 - empty_lines_ratio 0.148903 - comment_lines_ratio 0.057210 - function_lines_ratio 0.108150 - mean_lines_per_function 138.000000 - dtype: float64 - """ - df = modules_info_df(root, filepath_filt, index_field, on_error=on_error) - df = df.sum() - cols = set(df.index.values) - for col in ["empty_lines", "comment_lines", "doc_lines", "function_lines"]: - if {col, "lines"}.issubset(cols): - df[f"{col}_ratio"] = df[col] / df["lines"] - if {"num_of_functions", "function_lines"}.issubset(cols): - df["mean_lines_per_function"] = ( - df["function_lines"] / df["num_of_functions"] - ) - - return df
- - -
[docs]def stats_of( - modules, - filepath_filt=only_py_ext, - index_field=None, - on_error=DFLT_ON_ERROR, -): - """ - A dataframe of stats of the input modules. - - :param modules: list of importable names - :param root: module or directory path - :param filepath_filt: filepath filter function or regular expression - :param index_field: function or field string that should be used for the indexing of modules - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', or 'raise' - :return: - - >>> stats_of(['urllib', 'json', 'collections']) - urllib json collections - empty_lines_ratio 0.157034 0.136818 0.148903 - comment_lines_ratio 0.074142 0.038432 0.057210 - function_lines_ratio 0.213907 0.449654 0.108150 - mean_lines_per_function 13.463768 41.785714 138.000000 - lines 4343.000000 1301.000000 1276.000000 - empty_lines 682.000000 178.000000 190.000000 - comment_lines 322.000000 50.000000 73.000000 - docs_lines 425.000000 218.000000 133.000000 - function_lines 929.000000 585.000000 138.000000 - num_of_functions 69.000000 14.000000 1.000000 - num_of_classes 55.000000 3.000000 34.000000 - """ - import pandas as pd - - if isinstance(modules, str): - modules = [modules] - df = pd.concat([modules_info_df_stats(x) for x in modules], axis=1) - df.columns = modules - put_at_the_end = [ - x for x in df.index if x.endswith("lines") or x.startswith("num") - ] - put_at_the_front = [x for x in df.index if x not in put_at_the_end] - df = df.loc[put_at_the_front + put_at_the_end] - return df
- - -import collections - -if __name__ == "__main__": - from py2store.util import ModuleNotFoundErrorNiceMessage - - with ModuleNotFoundErrorNiceMessage(): - import argh - - argh.dispatch_commands( - [modules_info_df, modules_info_df_stats, stats_of] - ) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/examples/dropbox_w_urllib.html b/docs/_modules/py2store/examples/dropbox_w_urllib.html deleted file mode 100644 index 5af1c64..0000000 --- a/docs/_modules/py2store/examples/dropbox_w_urllib.html +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - - - py2store.examples.dropbox_w_urllib — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.examples.dropbox_w_urllib

-"""Dropbox read access with mapping interface -- using only builtins"""
-import os
-import zipfile
-import tempfile
-import urllib.request
-from py2store.base import KvReader
-
-
-
[docs]class DropboxFolderCopyReader(KvReader): - """Makes a full local copy of the folder (by default, to a local temp folder) and gives access to it. - """ - - def __init__(self, url, path=tempfile.gettempdir()): - self.url = url - self.path = path - - os.makedirs(self.path, exist_ok=True) - self._zip_filepath = os.path.join(self.path, 'shared_folder.zip') - - self._files = [] - self._get_folder() - - def __getitem__(self, rel_path): - real_path = os.path.join(self.path, rel_path) - try: - with open(real_path, 'r') as f: - return f.read() - except FileNotFoundError: - raise KeyError(f"Key doesn't exist: {rel_path}") - - def __iter__(self): - yield from sorted(path for path in self._files if not path == '/') - - def __contains__(self, rel_path): - return rel_path in self._files - - def _get_folder(self): - download_from_dropbox(self.url, self._zip_filepath) - self._unzip() - os.remove(self._zip_filepath) - - def _unzip(self): - with zipfile.ZipFile(self._zip_filepath, 'r') as zip_ref: - zip_ref.extractall(self.path) - self._files = zip_ref.namelist()
- - -
[docs]class DropboxFileCopyReader(KvReader): - def __init__(self, url, path=None): - self.url = url - self.path = path or self._get_filename_from_url() - - download_from_dropbox(self.url, self.path) - self.file = open(self.path, 'r') - - def __getitem__(self, index): - self.file.seek(0) - return self.readlines()[index] - - def __iter__(self): - self.file.seek(0) - yield from self.file - - def __len__(self): - return len(self.readlines()) - - def __contains__(self, k): - return k in self.read() - - def __del__(self): - if self.file: - self.file.close() - self.file = None - - def read(self): - self.file.seek(0) - return self.file.read() - - def readlines(self): - self.file.seek(0) - return self.file.readlines() - - def _get_filename_from_url(self): - # 'https:...txt?dl=0&smth=else' -> 'https:...txt' - url_w_no_params = self.url.split('?', 1)[0] - - # 'https://www.dropbox.com/.../my_file.txt' -> 'my_file.txt' - last_part_of_urls_path = url_w_no_params.rsplit('/', 1)[-1] - return last_part_of_urls_path
- - -DFLT_USER_AGENT = 'Wget/1.16 (linux-gnu)' - - -def download_from_dropbox( - url, file, chk_size=1024, user_agent=DFLT_USER_AGENT -): - def iter_content_and_copy_to(file): - req = urllib.request.Request(url) - req.add_header('user-agent', user_agent) - with urllib.request.urlopen(req) as response: - while True: - chk = response.read(chk_size) - if len(chk) > 0: - file.write(chk) - else: - break - - if not isinstance(file, str): - iter_content_and_copy_to(file) - else: - with open(file, 'wb') as _target_file: - iter_content_and_copy_to(_target_file) - - -def bytes_from_dropbox(url, chk_size=1024, user_agent=DFLT_USER_AGENT): - from io import BytesIO - - with BytesIO() as file: - download_from_dropbox( - url, file, chk_size=chk_size, user_agent=user_agent - ) - file.seek(0) - return file.read() - - -# DFLT_USER_AGENT = 'Wget/1.16 (linux-gnu)' -# def download_from_dropbox(url, file, as_zip=False, chunk_size=1024, user_agent=DFLT_USER_AGENT): -# -# response = requests.get( -# url, -# params={'dl': int(as_zip)}, -# headers={'user-agent': user_agent}, -# stream=True, -# ) -# -# def iter_content_and_copy_to(file): -# for chunk in response.iter_content(chunk_size=chunk_size): -# if chunk: -# file.write(chunk) -# -# if not isinstance(file, str): -# iter_content_and_copy_to(file) -# else: -# with open(file, 'wb') as _target_file: -# iter_content_and_copy_to(_target_file) -# -# -# def bytes_from_dropbox(url, chunk_size=1024, user_agent=DFLT_USER_AGENT): -# from io import BytesIO -# with BytesIO() as file: -# download_from_dropbox(url, file, chunk_size=chunk_size, user_agent=user_agent) -# file.seek(0) -# return file.read() -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/examples/kv_walking.html b/docs/_modules/py2store/examples/kv_walking.html deleted file mode 100644 index 78bf990..0000000 --- a/docs/_modules/py2store/examples/kv_walking.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - - - - - py2store.examples.kv_walking — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.examples.kv_walking

-"""
-walking through kv stores
-"""
-from py2store import cached_keys, KvReader
-from py2store.util import copy_attrs
-from collections.abc import Mapping
-
-
-# Pattern:
-
[docs]@cached_keys(keys_cache=set, name='SrcReader') -class SrcReader(KvReader): - def __init__(self, src, src_to_keys, key_to_obj): - self.src = src - self.src_to_keys = src_to_keys - self.key_to_obj = key_to_obj - copy_attrs( - src, to_obj=self, attrs=('__name__', '__qualname__', '__module__') - ) - - def __iter__(self): - yield from self.src_to_keys(self, self.src) - - def __getitem__(self, k): - return self.key_to_obj(self, self.src, k)
- - # def __repr__(self): - # return f"{self.__class__.__qualname__}({self.src}, {self._key_filt})" - - -inf = float('infinity') - - -def print_call(func, name=None, really=True): - func_name = name or getattr(func, '__name__', func) - - def _func(*args, **kwargs): - if really: - print(f'Calling {func_name} with {args=} and {kwargs=}') - return func(*args, **kwargs) - - return _func - - -def val_is_mapping(p, k, v): - return isinstance(v, Mapping) - - -def asis(p, k, v): - return p, k, v - - -def tuple_keypath_and_val(p, k, v): - if p == (): # we're just begining (the root), - p = (k,) # so begin the path with the first key. - else: - p = (*p, k) # extend the path (append the new key) - return p, v - - -DO_NOT_YIELD = type('DoNotYield', (), {})() - - -# TODO: More docs and doctests. This one even merits an extensive usage and example tutorial! -
[docs]def kv_walk( - v: Mapping, - yield_func=asis, # (p, k, v) -> what you want the gen to yield - walk_filt=val_is_mapping, # (p, k, v) -> whether to explore the nested structure v further - pkv_to_pv=tuple_keypath_and_val, - p=(), -): - """ - - :param v: - :param yield_func: (pp, k, vv) -> what ever you want the gen to yield - :param walk_filt: (p, k, vv) -> (bool) whether to explore the nested structure v further - :param pkv_to_pv: (p, k, v) -> (pp, vv) - where pp is a form of p + k (update of the path with the new node k) - and vv is the value that will be used by both walk_filt and yield_func - :param p: The path to v - - >>> d = {'a': 1, 'b': {'c': 2, 'd': 3}} - >>> list(kv_walk(d)) - [(('a',), 'a', 1), (('b',), 'b', {'c': 2, 'd': 3}), (('b', 'c'), 'c', 2), (('b', 'd'), 'd', 3)] - >>> list(kv_walk(d, lambda p, k, v: '.'.join(p))) - ['a', 'b', 'b.c', 'b.d'] - >>> list(kv_walk(d, lambda p, k, v: '.'.join(p))) - ['a', 'b', 'b.c', 'b.d'] - """ - # print(f"1: entered with: v={v}, p={p}") - for k, vv in v.items(): - # print(f"2: item: k={k}, vv={vv}") - pp, vv = pkv_to_pv( - p, k, vv - ) # update the path with k (and preprocess v if necessary) - to_yield = yield_func(pp, k, vv) - if to_yield is not DO_NOT_YIELD: - yield to_yield - if walk_filt( - p, k, vv - ): # should we recurse? (based on some function of p, k, v) - # print(f"3: recurse with: pp={pp}, vv={vv}\n") - yield from kv_walk( - vv, yield_func, walk_filt, pkv_to_pv, pp - ) # recurse
- # else: - # print(f"4: yield_func(pp={pp}, k={k}, vv={vv})\n --> {yield_func(pp, k, vv)}") - - # yield yield_func(pp, k, vv) # yield something computed from p, k, vv - - -from inspect import signature - - -
[docs]def conjunction(*funcs, name=None): - """Make a function that is the conjunction of other functions. - And by that we mean that - ``` - conjunction(*args, **kwargs) - ``` - will be equal to - ``` - func_1(*args, **kwargs) & ... & func_n(*args, **kwargs) - ``` - for all `args, kwargs`. - """ - first_func, *other_funcs = funcs - - def conjunct(*args, **kwargs): - result = first_func(*args, **kwargs) - for func in other_funcs: - result &= func(*args, **kwargs) - return result - - conjunct.funcs = funcs - conjunct.__signature__ = signature(first_func) - conjunct.__return_annotation__ = signature(funcs[-1]).return_annotation - if name is not None: - conjunct.__name__ = name - - return conjunct
- - -def until_max_path_length(max_path_length: int = 1): - def max_path_length_walk_filt(p, k, v): - return len(p) <= max_path_length - - return max_path_length_walk_filt -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/examples/last_key_inserted.html b/docs/_modules/py2store/examples/last_key_inserted.html deleted file mode 100644 index 0680aca..0000000 --- a/docs/_modules/py2store/examples/last_key_inserted.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - - py2store.examples.last_key_inserted — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.examples.last_key_inserted

-"""
-showing how to add the knowledge of the 'last key inserted' to stores
-"""
-from functools import wraps, partial
-
-NoKeyWasWrittenToYet = type('NoKeyWasWrittenToYet', (), {})()
-
-
-
[docs]def remember_last_key_written_to( - cls=None, *, only_if_new_key=False, name=None, same_name_as_class=False -): - """A decorator to get a class that remembers the last key that was written to. - Note that this is the last key that THIS STORE wrote to, not the last key that was - written to the DB. - - Some would say "it's not thread-safe", but that statement might be overkill. - See the code to see what you should or should not expect. - - :param cls: The class you want to decorate (omit if you want to make a decorator factory instead) - :param only_if_new_key: If by "last written to" we mean "last created" - (i.e. only keep track if the key is NEW, not if we just updated the value) - :param name: The name you want the decorated class to have - :param same_name_as_class: If you want to use the name of the decorated class itself. - - :return: A decorated class (if the class was given), or a class decorator (if cls=None). - - >>> def test(s): - ... # test: - ... s['hello'] = 'you' - ... assert s._last_key_written_to == 'hello' - ... s['goodbye'] = 'them' - ... assert s._last_key_written_to == 'goodbye' - ... s['hello'] = 'you' - ... assert s._last_key_written_to == 'hello' - ... - >>> - >>> S = remember_last_key_written_to(dict) - >>> s = S() - >>> test(s) - >>> - >>> # Use as decorator factory - >>> @remember_last_key_written_to - ... class SS(dict): - ... pass - ... - >>> - >>> ss = SS() - >>> test(ss) - >>> - >>> SSS = remember_last_key_written_to(dict, only_if_new_key=True) - >>> sss = SSS() - >>> assert sss._last_key_written_to is None - >>> sss['hi'] = 'there' - >>> sss._last_key_written_to - 'hi' - >>> sss[3] = .1415 - >>> sss._last_key_written_to - 3 - >>> sss['hi'] = 'this key already exists!' - >>> sss._last_key_written_to # so this should still be 3 - 3 - """ - if cls is None: - return partial( - remember_last_key_written_to, name=name, same_name_as_class=True - ) - else: - - class S(cls): - @wraps(cls.__init__) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._last_key_written_to = None - - if only_if_new_key: - - def __setitem__(self, k, v): - if k not in self: - self._last_key_written_to = k - return super().__setitem__(k, v) - - else: - - def __setitem__(self, k, v): - self._last_key_written_to = k - return super().__setitem__(k, v) - - if name is not None: - S.__name__ = name - elif same_name_as_class: - S.__name__ = cls.__name__ - - return S
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/examples/python_code_stats.html b/docs/_modules/py2store/examples/python_code_stats.html deleted file mode 100644 index 773c2fc..0000000 --- a/docs/_modules/py2store/examples/python_code_stats.html +++ /dev/null @@ -1,535 +0,0 @@ - - - - - - - - py2store.examples.python_code_stats — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.examples.python_code_stats

-"""
-Get stats about packages. Your own, or other's.
-Things like...
-
->>> import collections
->>> modules_info_df(collections)
-                      lines  empty_lines  ...  num_of_functions  num_of_classes
-collections.__init__   1273          189  ...                 1               9
-collections.abc           3            1  ...                 0              25
-<BLANKLINE>
-[2 rows x 7 columns]
->>> modules_info_df_stats(collections.abc)
-lines                      1276.000000
-empty_lines                 190.000000
-comment_lines                73.000000
-docs_lines                  133.000000
-function_lines              138.000000
-num_of_functions              1.000000
-num_of_classes               34.000000
-empty_lines_ratio             0.148903
-comment_lines_ratio           0.057210
-function_lines_ratio          0.108150
-mean_lines_per_function     138.000000
-dtype: float64
->>> stats_of(['urllib', 'json', 'collections'])
-                              urllib         json  collections
-empty_lines_ratio           0.157034     0.136818     0.148903
-comment_lines_ratio         0.074142     0.038432     0.057210
-function_lines_ratio        0.213907     0.449654     0.108150
-mean_lines_per_function    13.463768    41.785714   138.000000
-lines                    4343.000000  1301.000000  1276.000000
-empty_lines               682.000000   178.000000   190.000000
-comment_lines             322.000000    50.000000    73.000000
-docs_lines                425.000000   218.000000   133.000000
-function_lines            929.000000   585.000000   138.000000
-num_of_functions           69.000000    14.000000     1.000000
-num_of_classes             55.000000     3.000000    34.000000
-"""
-import re
-import os
-from py2store import filt_iter
-from py2store.sources import Attrs
-from py2store.filesys import FileStringReader
-from types import FunctionType, ModuleType
-from inspect import getsource
-
-psep = os.path.sep
-
-DFLT_ON_ERROR = "ignore"  # could be 'print', 'ignore', or 'raise'
-
-empty_line = re.compile("^\s*$")
-comment_line_p = re.compile("^\s+#.+$")
-line_p = re.compile("\n|\r|\n\r|\r\n")
-only_py_ext = lambda path: path.endswith(".py")
-no_test_folder = lambda path: "test" not in path.split(psep)
-
-
-def lines(string):
-    return line_p.split(string)
-
-
-def _num_lines_of_function_code(obj: FunctionType):
-    assert isinstance(obj, FunctionType)
-    return len(getsource(obj).split("\n")) - len(
-        (obj.__doc__ or "").split("\n")
-    )
-
-
-def _root_dir_and_name(root):
-    """
-    The parent path and leaf name for the given root
-    :param root: module instance, dot path, or directory path
-    :return:
-    """
-    if isinstance(root, str) and not os.path.dirname(root):
-        root = __import__(
-            root
-        )  # assume it's a dot path string of a module and try to import it
-    if isinstance(root, ModuleType):
-        root = os.path.dirname(root.__file__)
-    if root.endswith(psep):
-        root = root[:-1]
-    return os.path.dirname(root), os.path.basename(root)
-
-
-# TODO: Must be a standard lib for this!
-def _path_to_module_str(path, root_path):
-    """
-    The dot-path module string for a path (given the root_path, assumed to be on the python path)
-    :param path: The path to the module.
-    :param root_path: The path that's assumed to be on the python path
-    :return:
-    """
-    assert path.endswith(".py")
-    path = path[:-3]
-
-    if root_path.endswith(psep):
-        root_path = root_path[:-1]
-    root_path, root_package = _root_dir_and_name(root_path)
-    len_root = len(root_path) + 1
-    path_parts = path[len_root:].split(psep)
-    if path_parts[-1] == "__init__.py":
-        path_parts = path_parts[:-1]
-    return ".".join(path_parts)
-
-
-
[docs]def modules_info_gen(root, filepath_filt=only_py_ext, on_error=DFLT_ON_ERROR): - """ - Yields statistics (as dicts) of modules under the root module or directory of a python package. - - :param root: module instance, dot path, or directory path - :param filepath_filt: filepath filter function or regular expression - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', 'raise', or 'yield' - :return: A generator of dicts - """ - """Gives us statistics given the root module or directory of a python package""" - if isinstance(filepath_filt, str): - filt_pattern = re.compile(filepath_filt) - filepath_filt = filt_pattern.match - - parent_dir, dirname = _root_dir_and_name(root) - root = os.path.join(parent_dir, dirname) - - @filt_iter(filepath_filt) - class PyCodeReader(FileStringReader): - pass - - pycode = PyCodeReader(root) - - for k, code_str in pycode.items(): - try: - name = _path_to_module_str(k, root) - module_store = Attrs.module_from_path( - k, key_filt=lambda x: not x.startswith("__"), name=name - ) - - def obj_filt( - obj, - ): # to make sure we only analyze objects defined in module itself, not imported - obj_module = getattr(obj, "__module__", None) - if obj_module: - return obj_module == name - - objs = list( - filter(obj_filt, (vv._source for vv in module_store.values())) - ) - yield { - "filepath": k, - "lines": len(lines(code_str)), - "empty_lines": sum( - bool(empty_line.match(line)) for line in lines(code_str) - ), - "comment_lines": sum( - bool(comment_line_p.match(line)) - for line in lines(code_str) - ), - "docs_lines": sum( - len(lines(obj.__doc__ or "")) for obj in objs - ), - "function_lines": sum( - _num_lines_of_function_code(obj) - for obj in objs - if isinstance(obj, FunctionType) - ), - "num_of_functions": sum( - isinstance(obj, FunctionType) for obj in objs - ), - "num_of_classes": sum(isinstance(obj, type) for obj in objs), - } - except Exception as e: - if on_error == "print": - print(f"Problem with {k}: {str(e)[:50]}\n") - elif on_error == "ignore": - pass - elif on_error == "yield": - yield {"filepath": k, "error": e} - else: - raise
- - -
[docs]def modules_info_df( - root, filepath_filt=only_py_ext, index_field=None, on_error=DFLT_ON_ERROR -): - """ - A pandas DataFrame of stats of the root (package or directory thereof). - :param root: module or directory path - :param filepath_filt: filepath filter function or regular expression - :param index_field: function or field string that should be used for the indexing of modules - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', or 'raise' - :return: A DataFrame whose rows contain information for each module - - >>> import urllib - >>> modules_info_df(urllib) - lines empty_lines ... num_of_functions num_of_classes - urllib.error 78 18 ... 0 3 - urllib.request 2743 404 ... 23 28 - urllib.__init__ 1 1 ... 0 0 - urllib.response 81 24 ... 0 4 - urllib.robotparser 274 36 ... 0 4 - urllib.parse 1166 199 ... 46 16 - <BLANKLINE> - [6 rows x 7 columns] - """ - - import pandas as pd - - d = list(modules_info_gen(root, filepath_filt, on_error=on_error)) - - if index_field is None: - dirpath, dirname = _root_dir_and_name(root) - root = os.path.join(dirpath, dirname) - index_field = lambda x: _path_to_module_str(x.pop("filepath"), root) - - if isinstance(index_field, str): - return pd.DataFrame(d).set_index(index_field) - elif callable(index_field): - index = [index_field(x) for x in d] - return pd.DataFrame(index=index, data=d)
- - -
[docs]def modules_info_df_stats( - root, filepath_filt=only_py_ext, index_field=None, on_error=DFLT_ON_ERROR -): - """ - A pandas Series of statistics over all modules of some root (package or directory thereof). - :param root: module or directory path - :param filepath_filt: filepath filter function or regular expression - :param index_field: function or field string that should be used for the indexing of modules - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', or 'raise' - :return: A Series whose rows containing statistics - - >>> import json - >>> modules_info_df_stats(json) - lines 1301.000000 - empty_lines 178.000000 - comment_lines 50.000000 - docs_lines 218.000000 - function_lines 585.000000 - num_of_functions 14.000000 - num_of_classes 3.000000 - empty_lines_ratio 0.136818 - comment_lines_ratio 0.038432 - function_lines_ratio 0.449654 - mean_lines_per_function 41.785714 - dtype: float64 - >>> modules_info_df_stats('collections.abc') - lines 1276.000000 - empty_lines 190.000000 - comment_lines 73.000000 - docs_lines 133.000000 - function_lines 138.000000 - num_of_functions 1.000000 - num_of_classes 34.000000 - empty_lines_ratio 0.148903 - comment_lines_ratio 0.057210 - function_lines_ratio 0.108150 - mean_lines_per_function 138.000000 - dtype: float64 - """ - df = modules_info_df(root, filepath_filt, index_field, on_error=on_error) - df = df.sum() - cols = set(df.index.values) - for col in ["empty_lines", "comment_lines", "doc_lines", "function_lines"]: - if {col, "lines"}.issubset(cols): - df[f"{col}_ratio"] = df[col] / df["lines"] - if {"num_of_functions", "function_lines"}.issubset(cols): - df["mean_lines_per_function"] = ( - df["function_lines"] / df["num_of_functions"] - ) - - return df
- - -
[docs]def stats_of( - modules, - filepath_filt=only_py_ext, - index_field=None, - on_error=DFLT_ON_ERROR, -): - """ - A dataframe of stats of the input modules. - - :param modules: list of importable names - :param root: module or directory path - :param filepath_filt: filepath filter function or regular expression - :param index_field: function or field string that should be used for the indexing of modules - :param on_error: What to do when an error occurs when extracting information from a module. - Values are 'ignore', 'print', or 'raise' - :return: - - >>> stats_of(['urllib', 'json', 'collections']) - urllib json collections - empty_lines_ratio 0.157034 0.136818 0.148903 - comment_lines_ratio 0.074142 0.038432 0.057210 - function_lines_ratio 0.213907 0.449654 0.108150 - mean_lines_per_function 13.463768 41.785714 138.000000 - lines 4343.000000 1301.000000 1276.000000 - empty_lines 682.000000 178.000000 190.000000 - comment_lines 322.000000 50.000000 73.000000 - docs_lines 425.000000 218.000000 133.000000 - function_lines 929.000000 585.000000 138.000000 - num_of_functions 69.000000 14.000000 1.000000 - num_of_classes 55.000000 3.000000 34.000000 - """ - import pandas as pd - - if isinstance(modules, str): - modules = [modules] - df = pd.concat([modules_info_df_stats(x) for x in modules], axis=1) - df.columns = modules - put_at_the_end = [ - x for x in df.index if x.endswith("lines") or x.startswith("num") - ] - put_at_the_front = [x for x in df.index if x not in put_at_the_end] - df = df.loc[put_at_the_front + put_at_the_end] - return df
- - -import collections - -if __name__ == "__main__": - from py2store.util import ModuleNotFoundErrorNiceMessage - - with ModuleNotFoundErrorNiceMessage(): - import argh - - argh.dispatch_commands( - [modules_info_df, modules_info_df_stats, stats_of] - ) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/examples/write_caches.html b/docs/_modules/py2store/examples/write_caches.html deleted file mode 100644 index d2b54de..0000000 --- a/docs/_modules/py2store/examples/write_caches.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - - py2store.examples.write_caches — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.examples.write_caches

-"""
-stores that implement various write caching algorithms
-"""
-from py2store.utils.cumul_aggreg_write import (
-    join_byte_values_and_key_as_current_utc_milliseconds,
-    CumulAggregWriteWithAutoFlush,
-)
-
-
-def append_and_print_state(store, data):
-    store.append(data)
-    print(f'Appended [{data}].\tcache: {store.cache},\tstore: {store.store}')
-
-
-def timestamp_on_store():
-    s = CumulAggregWriteWithAutoFlush(
-        store={},
-        cache_to_kv=join_byte_values_and_key_as_current_utc_milliseconds,
-        flush_cache_condition=lambda x: len(x) >= 3,
-    )
-
-    append_and_print_state(s, b'Hello')
-    append_and_print_state(s, b'World')
-    append_and_print_state(s, b'!')
-    append_and_print_state(s, b'Wassup?')
-
-
-
[docs]def timestamp_on_cache_and_concatenate_all_values(): - """The cache timestamps (with system clock) every item on insertion (append) and uses the min timestamp as - a key for storage.""" - from collections import UserList - import time - - class TimestampedItemsCache(UserList): - def append(self, item): - ts = time.time() - super().append((ts, item)) - - def min_key_joined_values(items): - sorted_items = sorted(items, key=lambda x: x[0]) - k = sorted_items[0][0] - join_char = sorted_items[0][1][ - 0:0 - ] # better way to get '' or b'' according to data type? - yield k, join_char.join(x[1] for x in sorted_items) - - s = CumulAggregWriteWithAutoFlush( - store={}, - cache_to_kv=min_key_joined_values, - flush_cache_condition=lambda x: len(x) >= 3, - mk_cache=TimestampedItemsCache, - ) - - append_and_print_state(s, b'Hello') - time.sleep(0.1) - append_and_print_state(s, b'World') - time.sleep(0.1) - append_and_print_state(s, b'!') - time.sleep(0.1) - append_and_print_state(s, b'Wassup?') - time.sleep(0.1)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/audio.html b/docs/_modules/py2store/ext/audio.html deleted file mode 100644 index ef441ed..0000000 --- a/docs/_modules/py2store/ext/audio.html +++ /dev/null @@ -1,447 +0,0 @@ - - - - - - - - py2store.ext.audio — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.audio

-from io import BytesIO
-from py2store.stores.local_store import LocalBinaryStore
-from py2store.trans import add_wrapper_method
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-# TODO: Offer some functionality based on builtins only (and compare performance to soundfile equivalent)
-with ModuleNotFoundErrorNiceMessage():
-    import soundfile as sf
-
-# from py2store.mint import wraps, _empty_func
-
-DFLT_DTYPE = "int16"
-DFLT_FORMAT = "WAV"
-DFLT_N_CHANNELS = 1
-
-# TODO: Do some validation and smart defaults with these
-dtype_from_sample_width = {
-    1: "int16",
-    2: "int16",
-    3: "int32",
-    4: "int32",
-    8: "float64",
-}
-
-sample_width_for_soundfile_subtype = {
-    "DOUBLE": 8,
-    "FLOAT": 4,
-    "G721_32": 4,
-    "PCM_16": 2,
-    "PCM_24": 3,
-    "PCM_32": 4,
-    "PCM_U8": 1,
-}
-
-# soundfile_signature not used yet, but intended for a future version of this module, that will use minting
-# and signature injection instead of long copy pastes of
-soundfile_signature = dict(
-    dtype=DFLT_DTYPE, format=DFLT_FORMAT, subtype=None, endian=None
-)
-
-
-
[docs]class SampleRateAssertionError(ValueError): - ...
- - -class ReadAudioFileMixin: - read_kwargs = {} - - def _obj_of_data(self, data): - return sf.read(BytesIO(data), **self.read_kwargs) - - -class PcmSerializationTrans: - def __init__( - self, - sr, - channels=DFLT_N_CHANNELS, - dtype=DFLT_DTYPE, - format="RAW", - subtype="PCM_16", - endian=None, - ): - assert isinstance(sr, int), "assert_sr must be an int" - self.sr = sr - self._rw_kwargs = dict( - samplerate=sr, - channels=channels, - dtype=dtype, - format=format, - subtype=subtype, - endian=endian, - ) - - def _obj_of_data(self, data): - return sf.read(BytesIO(data), **self._rw_kwargs)[0] - - -@add_wrapper_method -class WfSrSerializationTrans: - _read_format = DFLT_FORMAT - _rw_kwargs = dict(dtype=DFLT_DTYPE, subtype=None, endian=None) - - def __init__( - self, dtype=DFLT_DTYPE, format=DFLT_FORMAT, subtype=None, endian=None - ): - self._read_format = format - self._rw_kwargs = dict(dtype=dtype, subtype=subtype, endian=endian) - - def _obj_of_data(self, data): - return sf.read(BytesIO(data), **self._rw_kwargs) - - def _data_of_obj(self, obj): - wf, sr = obj - b = BytesIO() - sf.write(b, wf, samplerate=sr, format=self._rw_kwargs) - b.seek(0) - return b.read() - - -@add_wrapper_method -class WfSerializationTrans(WfSrSerializationTrans): - def __init__( - self, - assert_sr=None, - dtype=DFLT_DTYPE, - format=DFLT_FORMAT, - subtype=None, - endian=None, - ): - super().__init__(dtype, format, subtype, endian) - self.assert_sr = assert_sr - - def _obj_of_data(self, data): - wf, sr = super()._obj_of_data(data) - if self.assert_sr is not None and sr != self.assert_sr: - raise SampleRateAssertionError( - f"{self.assert_sr} expected but I encountered {sr}" - ) - return wf - - def _data_of_obj(self, obj): - return super()._data_of_obj((obj, self.sr)) - - -@add_wrapper_method -class WavSerializationTrans: - _rw_kwargs = dict(format="WAV", subtype=None, endian=None) - _read_kwargs = dict(dtype=DFLT_DTYPE) - - def __init__( - self, - assert_sr=None, - dtype=DFLT_DTYPE, - format="WAV", - subtype=None, - endian=None, - ): - if assert_sr is not None: - assert isinstance(assert_sr, int), "assert_sr must be an int" - self.assert_sr = assert_sr - self._rw_kwargs = dict(format=format, subtype=subtype, endian=endian) - self._read_kwargs = dict(dtype=dtype) - - def _obj_of_data(self, data): - wf, sr = sf.read(BytesIO(data), **self._read_kwargs) - if self.assert_sr != sr: - if ( - self.assert_sr is not None - ): # Putting None check here because less common, so more efficient on avg - raise SampleRateAssertionError( - f"sr was {sr}, should be {self.assert_sr}" - ) - return wf - - def _data_of_obj(self, obj): - wf = obj - b = BytesIO() - sf.write(b, wf, samplerate=self.assert_sr, **self._rw_kwargs) - b.seek(0) - return b.read() - - -PcmSerializationMixin = PcmSerializationTrans # alias for back-compatibility -WfSrSerializationMixin = WfSrSerializationTrans # alias for back-compatibility -WavSerializationMixin = WavSerializationTrans # alias for back-compatibility -WfSerializationMixin = WfSerializationTrans # alias for back-compatibility - -from py2store.base import Store - - -
[docs]class WavLocalFileStore(WavSerializationTrans, LocalBinaryStore): - def __init__( - self, - path_format, - assert_sr=None, - max_levels=None, - dtype=DFLT_DTYPE, - format="WAV", - subtype=None, - endian=None, - ): - LocalBinaryStore.__init__(self, path_format, max_levels) - WavSerializationTrans.__init__( - self, - assert_sr=assert_sr, - dtype=dtype, - format=format, - subtype=subtype, - endian=endian, - )
- - -WavLocalFileStore2 = WavLocalFileStore # back-compatibility alias - -# Old WavLocalFileStore -# class WavLocalFileStore(Store): -# def __init__( -# self, -# path_format, -# assert_sr=None, -# max_levels=None, -# dtype=DFLT_DTYPE, -# format="WAV", -# subtype=None, -# endian=None, -# ): -# persister = LocalBinaryStore(path_format, max_levels) -# super().__init__(persister) -# t = WavSerializationTrans( -# assert_sr=assert_sr, -# dtype=dtype, -# format=format, -# subtype=subtype, -# endian=endian, -# ) -# self._obj_of_data = t._obj_of_data - - -# class WfSrLocalFileStore(LocalBinaryStore): -# """""" -# def _obj_of_data(self, data): -# return data -# return sf.read(BytesIO(data)) - - -from py2store.stores.local_store import MakeMissingDirsStoreMixin -import os - - -
[docs]class PcmSourceSessionBlockStore(MakeMissingDirsStoreMixin, LocalBinaryStore): - sep = os.path.sep - path_depth = 3 - - def _id_of_key(self, k): - raise DeprecationWarning("Deprecated") - assert len(k) == self.path_depth - return super()._id_of_key( - self.sep.join(self.path_depth * ["{}"]).format(*k) - ) - - def _key_of_id(self, _id): - raise DeprecationWarning("Deprecated") - key = super()._key_of_id(_id) - return tuple(key.split(self.sep))
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/dataframes.html b/docs/_modules/py2store/ext/dataframes.html deleted file mode 100644 index 6ce6d2a..0000000 --- a/docs/_modules/py2store/ext/dataframes.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - - - py2store.ext.dataframes — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.dataframes

-from py2store.util import ModuleNotFoundErrorNiceMessage
-from py2store.stores.local_store import LocalBinaryStore
-
-from io import BytesIO
-import os
-import pickle
-
-with ModuleNotFoundErrorNiceMessage():
-    import pandas as pd
-
-
-def df_from_data_given_ext(data, ext, **kwargs):
-    if ext.startswith("."):
-        ext = ext[1:]
-    if ext in {"xls", "xlsx"}:
-        kwargs = dict({"index": False}, **kwargs)
-        return pd.read_excel(data, **kwargs)
-    elif ext in {"csv"}:
-        kwargs = dict({"index_col": False}, **kwargs)
-        return pd.read_csv(data, **kwargs)
-    elif ext in {"tsv"}:
-        kwargs = dict({"sep": "\t", "index_col": False}, **kwargs)
-        return pd.read_csv(data, **kwargs)
-    elif ext in {"json"}:
-        kwargs = dict({"orient": "records"}, **kwargs)
-        return pd.read_json(data, **kwargs)
-    elif ext in {"html"}:
-        kwargs = dict({"index_col": False}, **kwargs)
-        return pd.read_html(data, **kwargs)[0]
-    elif ext in {"p", "pickle"}:
-        return pickle.load(data, **kwargs)
-    else:
-        raise ValueError(f"Don't know how to handle extension: {ext}")
-
-
-# TODO: Make the logic independent from local files assumption.
-# TODO: Better separate Reader, and add DfStore to make a writer.
-
-
-
[docs]class DfReader(LocalBinaryStore): - def __init__(self, path_format, ext_specs=None): - super().__init__(path_format) - if ext_specs is None: - ext_specs = {} - self.ext_specs = ext_specs - - def __getitem__(self, k): - _, ext = os.path.splitext(k) - if ext.startswith("."): - ext = ext[1:] - kwargs = self.ext_specs.get(ext, {}) - data = BytesIO(super().__getitem__(k)) - return df_from_data_given_ext(data, ext, **kwargs) - - def __setitem__(self, k, v): - raise NotImplementedError( - "This is a reader: No write operation allowed" - ) - - def __delitem__(self, k): - raise NotImplementedError( - "This is a reader: No delete operation allowed" - )
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/docx.html b/docs/_modules/py2store/ext/docx.html deleted file mode 100644 index 49e79ef..0000000 --- a/docs/_modules/py2store/ext/docx.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - py2store.ext.docx — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.docx

-from py2store.util import ModuleNotFoundErrorNiceMessage
-from py2store.stores.local_store import LocalBinaryStore
-from py2store import wrap_kvs
-
-from io import BytesIO
-
-with ModuleNotFoundErrorNiceMessage(
-        "docx wasn't found. Search and install python-docx package. "
-        "For example, you could do: pip install python-docx"
-):
-    import docx  # https://automatetheboringstuff.com/chapter13/
-
-
-
[docs]def get_text_from_docx(doc): - """Get text from docx.Document object. - More precisely, 'text' will be the newline-separated concatenation of the .text attributes of every paragraph. - You can got a document object from a file path of pointer f by doing: - import docx # pip install python-docx - doc = docx.Document(f) - """ - fullText = [] - for para in doc.paragraphs: - fullText.append(para.text) - return "\n".join(fullText)
- - -def bytes_to_doc(doc_bytes): - return docx.Document(BytesIO(doc_bytes)) - - -LocalDocxStore = wrap_kvs( - LocalBinaryStore, "LocalDocxStore", obj_of_data=bytes_to_doc -) - -LocalDocxTextStore = wrap_kvs( - LocalDocxStore, "LocalDocxTextStore", obj_of_data=get_text_from_docx -) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/github.html b/docs/_modules/py2store/ext/github.html deleted file mode 100644 index 25df267..0000000 --- a/docs/_modules/py2store/ext/github.html +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - py2store.ext.github — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.github

-from py2store import KvReader
-from github import GithubException
-
-# from py2store.util import lazyprop
-# from py2store.trans import cache_iter
-# from py2store.key_mappers.paths import PathGetMixin
-from github import Github, ContentFile
-
-from py2store.util import format_invocation
-
-
-#
-# from py2store.utils.signatures import update_signature_with_signatures_from_funcs
-#
-# # Just meant to be used for it's signature:
-# def _account_name(account_name): ...
-
-# @cache_iter(keys_cache=sorted)
-# @cache_iter
-
-
-def decoded_contents(content_file):
-    return content_file.decoded_content
-    # from base64 import b64decode
-    # return b64decode(content_file.content).decode()
-
-
-# TODO: use signature arithmetic
-# @kv_decorator
-
[docs]class GitHubReader(KvReader): - """ - a Store that can access a GitHub account - """ - - def __init__( - self, - account_name: str = None, - content_file_extractor=decoded_contents, - login_or_token=None, - password=None, - jwt=None, - base_url="https://api.github.com", - timeout=15, - client_id=None, - client_secret=None, - user_agent="PyGithub/Python", - per_page=30, - verify=True, - retry=None, - ): - - assert isinstance( - account_name, str - ), "account_name must be given (and a str)" - - _github = Github( - login_or_token=login_or_token, - password=password, - jwt=jwt, - base_url=base_url, - timeout=timeout, - client_id=client_id, - client_secret=client_secret, - user_agent=user_agent, - per_page=per_page, - verify=verify, - retry=retry, - ) - self._github = _github - self._source_obj = ( - _github.get_user(account_name) - if account_name - else _github.get_user() - ) - self.content_file_extractor = content_file_extractor - - def __iter__(self): - for x in self._source_obj.get_repos(): - org, name = x.full_name.split("/") - if org == self._source_obj.login: - yield name - - def __getitem__(self, k): - """Retrieves a given repository - :param k: str - :rtype: :class:`github.Repository.Repository` - """ - try: - repository = self._source_obj.get_repo(k) - except GithubException as e: - raise KeyError(f"Key doesn't exist: {k}") - return Branches(repository, self.content_file_extractor) - - def __repr__(self): - return format_invocation( - self.__class__.__name__, - (self._source_obj, self.content_file_extractor), - )
- - -
[docs]class Branches(KvReader): - def __init__( - self, repository_obj, content_file_extractor=decoded_contents - ): - self._source_obj = repository_obj - self.content_file_extractor = content_file_extractor - # self._con = repository_obj # same as this. - - def __iter__(self): - yield from (x.name for x in self._source_obj.get_branches()) - - def __getitem__(self, k): - # return self._source_obj.get_branch(k) # should not give only the branch - # return self._source_obj.get_contents("", ref = k) - return BranchDir( - self._source_obj, - branch_name=k, - path="", - content_file_extractor=self.content_file_extractor, - ) - - def __repr__(self): - return format_invocation( - self.__class__.__name__, - (self._source_obj, self.content_file_extractor), - )
- - -
[docs]class BranchDir(KvReader): - def __init__( - self, - repository_obj, - branch_name, - path="", - content_file_extractor=decoded_contents, - ): - self._source_obj = repository_obj - self.branch_name = branch_name - self.path = path - self.content_file_extractor = content_file_extractor - - def __iter__(self): - yield from ( - self.path + "/" + x.name - for x in self._source_obj.get_contents( - self.path, ref=self.branch_name - ) - ) - # yield from (x.name for x in self._source_obj.get_contents(self.subpath, ref=self.branch_name)) - - def __getitem__(self, k): - t = self._source_obj.get_contents(k) - # TODO: There is an inefficiency here in the isinstance(t, list) case - if isinstance( - t, list - ): # TODO: ... you already have the content_files in t, so don't need to call API again. - return self.__class__( - self._source_obj, - self.branch_name, - k, - self.content_file_extractor, - ) - else: - return self.content_file_extractor(t) - - def __repr__(self): - return format_invocation( - self.__class__.__name__, - ( - self._source_obj, - self.branch_name, - self.path, - self.content_file_extractor, - ), - )
- # return f"{self.__class__.__name__}({self._source_obj}, {self.branch_name})" - - -# Not used, but for principle: - - -def _content_file_isfile(content_file): - return content_file.type == "file" - - -def _content_file_isdir(content_file): - return content_file.type == "dir" - -# from py2store import kv_wrap -# -# BranchContent = kv_wrap.outcoming_vals(lambda x: x.contents if isinstance(x, )) - - -# class GitDir: -# def __init__(self, content_files): -# self.content_files = content_files - -# @update_signature_with_signatures_from_funcs(Github.__init__, _account_name) -# def __init__(self, account_name, *args, **kwargs): -# if len(args) > 0: -# account_name = args[0] -# else: -# account_name = kwargs.pop('account_name', None) -# assert isinstance(account_name, str), "account_name must be given (and a str)" -# -# _source_obj = Github(*args, **kwargs) -# self._access_args = (args, kwargs) -# self._source_obj = _source_obj.get_user(account_name) if account_name else _source_obj.get_user() - -# from py2store import KvReader -# from py2store import Store, Persister -# from github import GithubException -# from py2store.util import lazyprop -# from py2store.trans import cache_iter -# from py2store.key_mappers.paths import PathGetMixin -# -# -# # @cache_iter(keys_cache=sorted) -# # @cache_iter -# -# def check_credentials(account_name, access_token): -# if access_token is None: -# raise Exception('An access_token must be provided') -# return account_name -# -# -# # @kv_decorator -# class GitHubReader(KvReader): -# """ -# a Store that can access a GitHub account -# """ -# -# def __init__(self, account_name=None, access_token=None, access_kwargs=None): -# account_name = check_credentials(account_name, access_token) -# _source_obj = Github(login_or_token=access_token) -# self._access_kwargs = access_kwargs -# self._source_obj = _source_obj.get_user(account_name) if account_name else _source_obj.get_user() -# -# def __iter__(self): -# for x in self._source_obj.get_repos(): -# org, name = x.full_name.split('/') -# if org == self._source_obj.login: -# yield name -# -# def __getitem__(self, k): -# """Retrieves a given repository -# :param k: str -# :rtype: :class:`github.Repository.Repository` -# """ -# try: -# repository = self._source_obj.get_repo(k) -# except GithubException as e: -# raise KeyError(f"Key doesn't exist: {k}") -# return Branches(repository) -# -# -# class Branches(KvReader): -# def __init__(self, repository_obj): -# self._source_obj = repository_obj -# # self._con = repository_obj # same as this. -# -# def __iter__(self): -# yield from (x.name for x in self._source_obj.get_branches()) -# -# def __getitem__(self, k): -# # return self._source_obj.get_branch(k) # should not give only the branch -# # return self._source_obj.get_contents("", ref = k) -# return BranchContent(self._source_obj, k) -# -# -# class BranchContent(KvReader): -# def __init__(self, repository_obj, branch_name): -# self._source_obj = repository_obj -# self.branch_name = branch_name -# -# def __iter__(self): -# yield from (x.name for x in self._source_obj.get_contents("", ref=self.branch_name)) -# -# def __getitem__(self, k): -# return self._source_obj.get_contents(k) - - -# from py2store import KvReader -# from py2store import Store, Persister -# from github import GithubException -# from py2store.util import lazyprop -# from py2store.trans import cache_iter -# from github import Github -# -# -# # @cache_iter(keys_cache=sorted) -# # @cache_iter -# class GitHubReader(KvReader): -# """ -# a Store that can access a GitHub account -# """ -# -# def __init__(self, access_token, access_kwargs=None): -# _source_obj = Github(login_or_token=access_token) -# self._access_kwargs = access_kwargs -# self._source_obj = _source_obj.get_user() -# -# def __iter__(self): -# for x in self._source_obj.get_repos(): -# org, name = x.full_name.split('/') -# if org == self._source_obj.login: -# yield name -# -# # yield from (x.full_name.split('/')[-1] for x in self._source_obj.get_repos()) -# -# def __getitem__(self, k): -# """Retrieves a given repository -# :param k: str -# :rtype: :class:`github.Repository.Repository` -# """ -# try: -# repository = self._source_obj.get_repo(k) -# except GithubException as e: -# raise KeyError(f"Key doesn't exist: {k}") -# -# return Branches(repository) -# -# -# class Branches(KvReader): -# def __init__(self, repository_obj): -# self._source_obj = repository_obj -# # self._con = repository_obj # same as this. -# -# def __iter__(self): -# yield from (x.name for x in self._source_obj.get_branches()) -# -# def __getitem__(self, k): -# return self._source_obj.get_branch(k) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/gitlab.html b/docs/_modules/py2store/ext/gitlab.html deleted file mode 100644 index 21fa921..0000000 --- a/docs/_modules/py2store/ext/gitlab.html +++ /dev/null @@ -1,486 +0,0 @@ - - - - - - - - py2store.ext.gitlab — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.gitlab

-import requests
-import os
-from warnings import warn
-from operator import itemgetter
-
-# if you want to use that environment variable
-dflt_private_token = os.getenv("PY2STORE_GITLAB_API_TOKEN", None)
-
-from py2store.util import lazyprop
-from py2store import KvReader
-from py2store.utils.uri_utils import mk_str_making_func
-
-url_templates = dict(
-    project_names="projects/",
-    branche_names="projects/{project_id}/repository/branches",
-    branch="projects/{project_id}/repository/branches/{branch_name}",
-    commit_by_sha="projects/{project_id}/repository/commits/{commit_sha}",
-    commit_message_by_sha="projects/{project_id}/repository/commits/{commit_sha}",
-    commit_date_by_sha="projects/{project_id}/repository/commits/{commit_sha}",
-    commit_diff_by_sha="projects/{project_id}/repository/commits/{commit_sha}/diff",
-    file_from_repository="projects/{project_id}/repository/files/{file_path}",
-    file_from_repository_raw="projects/{project_id}/repository/files/{file_path}/raw",
-    project_files="projects/{project_id}/repository/tree",
-    tags_list="projects/{project_id}/repository/tags",
-)
-
-
-def mk_url_factory(base_url):
-    if not base_url.endswith("/"):
-        base_url = base_url + "/"
-
-    class url_for:
-        pass
-
-    for name, format in url_templates.items():
-        func = mk_str_making_func(
-            base_url + format, module=__name__, name=name
-        )
-        setattr(url_for, name, staticmethod(func))
-
-    return url_for()
-
-
-#
-#     for k, v in Formats.__dict__.items():
-#         def url(self):
-#             return self.base_url + v
-#         locals()['get_'  + k] = lazyprop(lambda self: self.base_url + getattr(self, k))
-
-
-
[docs]class GitlabProjectReader(KvReader): - api_url_format = "{base_url}/api/v4/" - - def __init__(self, base_url, private_token=dflt_private_token): - if dflt_private_token is None: - warn( - "No private_token was given. " - "Provide it at instance creation time " - "or as the PY2STORE_GITLAB_API_TOKEN environment, " - "or however you wish (and if you're silly, you'll know who to blame!)" - ) - - self.base_url = base_url - self._private_token = private_token - self._git_api_url = self.api_url_format.format(base_url=base_url)
- - -# https://docs.gitlab.com/ce/api/README.html -class GitLabAccessor(object): - api_url_format = "{base_url}/api/v4/" - - def __init__( - self, - base_url="http://git.otosense.ai/", - project_name=None, - private_token=dflt_private_token, - ): - - self.git_api_url = self.api_url_format.format(base_url=base_url) - - self.project_name = project_name - self._headers = {"content-type": "application/json"} - self._private_token = private_token - if self._private_token: - self._headers["Private-Token"] = self._private_token - - self._set_project_id() - self.request = get_request_constructor() - - def _get_stuff_from_url( - self, url, output_trans=None, response_attr="json", params=None - ): - response = requests.get(url, headers=self._headers, params=params) - if response_attr == "json": - x = response.json() - elif response_attr == "content": - x = response.content - else: - raise ValueError(f"Unknown response_attr: {response_attr}") - - if response.status_code != 200: - raise Exception(f"{response.json()}") - - if output_trans is not None: - return output_trans(x) - else: - return x - - def _get_json_from_url(self, url, output_trans=None, params=None): - if isinstance(output_trans, str): - field = output_trans - output_trans = itemgetter(field) - return self._get_stuff_from_url( - url, output_trans, response_attr="json", params=params - ) - - def set_project(self, project_name=None): - if project_name is None: - warn( - "You need to specify a project_name. I'm returning a list of project_name for ya." - ) - return self.get_project_names() - else: - self.project_name = project_name - self._set_project_id() - - def _set_project_id(self): - url = "{}/projects/".format(self.git_api_url) - params = {"search": self.project_name} - response = requests.get(url, params=params, headers=self._headers) - - if response.status_code != 200: - raise Exception(f"{response.json()}") - else: - projects = response.json() - - if len(projects) == 0: - raise Exception( - "No Projects with name {} found.".format(self.project_name) - ) - - self.project_id = projects[0]["id"] - - def get_project_names(self): - url = "{}projects/".format(self.git_api_url) - return self._get_json_from_url( - url, output_trans=lambda jdict: [x["name"] for x in jdict] - ) - # response = requests.get(url, headers=self._headers) - # return [x['name'] for x in response.json()] - - def get_branch_names(self): - url = "{}projects/{}/repository/branches".format( - self.git_api_url, self.project_id - ) - return self._get_json_from_url( - url, output_trans=lambda jdict: [x["name"] for x in jdict] - ) - # response = requests.get(url, headers=self._headers) - # return [x['name'] for x in response.json()] - - def get_branch(self, branch_name=None): - if branch_name is None: - warn( - "You need to specify a branch. I'm returning a list of branches for ya." - ) - return self.get_branch_names() - else: - url = "{}projects/{}/repository/branches/{}".format( - self.git_api_url, self.project_id, branch_name - ) - return self._get_json_from_url(url) - # - # response = requests.get(url, headers=self._headers) - # branch = response.json() - # - # return branch - - def get_commit_by_sha(self, commit_sha): - url = "{}projects/{}/repository/commits/{}".format( - self.git_api_url, self.project_id, commit_sha - ) - return self._get_json_from_url(url) - # - # response = requests.get(url, headers=self._headers) - # - # commit = response.json() - # - # return commit - - def get_commit_message_by_sha(self, commit_sha): - url = "{}projects/{}/repository/commits/{}".format( - self.git_api_url, self.project_id, commit_sha - ) - return self._get_json_from_url(url, output_trans="message") - # response = requests.get(url, headers=self._headers) - # - # commit = response.json() - # - # return commit['message'] - - def get_commit_date_by_sha(self, commit_sha): - url = "{}projects/{}/repository/commits/{}".format( - self.git_api_url, self.project_id, commit_sha - ) - return self._get_json_from_url(url, output_trans="date") - # response = requests.get(url, headers=self._headers) - # - # commit = response.json() - # - # return commit['date'] - - def get_commit_diff_by_sha(self, commit_sha): - url = "{}projects/{}/repository/commits/{}/diff".format( - self.git_api_url, self.project_id, commit_sha - ) - return self._get_json_from_url(url) - # - # response = requests.get(url, headers=self._headers) - # - # diff = response.json() - # - # return diff - - def get_file_from_repository(self, file_path, ref="master", raw=False): - url = "{}projects/{}/repository/files/{}".format( - self.git_api_url, - self.project_id, - file_path.replace("/", "%2F").replace(".", "%2E"), - ) - if raw: - url = "{}projects/{}/repository/files/{}/raw".format( - self.git_api_url, self.project_id, file_path - ) - - params = {"ref": ref} - return self._get_stuff_from_url( - url, output_trans=None, response_attr="content", params=params - ) - # response = requests.get(url, params=params, headers=headers) - # file = response.content - # return file - - def get_project_files(self): - - url = "{}projects/{}/repository/tree".format( - self.git_api_url, self.project_id - ) - return self._get_json_from_url(url) - # response = requests.get(url, headers=self._headers) - # - # files = response.json() - # - # return files - - def get_tags_list(self): - - url = "{}projects/{}/repository/tags".format( - self.git_api_url, self.project_id - ) - return self._get_json_from_url(url) - # response = requests.get(url, headers=self._headers) - # - # tags = response.json() - # - # return tags - - -def get_request_constructor(): - pass - - -if __name__ == "__main__": - ogl = GitLabAccessor(base_url="http://git.otosense.ai/", project_name=None) - - print(ogl.get_project_names()) # prints all project names - ogl.set_project("ssofnad") # sets the project to ssofnad - print( - ogl.get_branch_names() - ) # gets the branch names of current project (as set previously) - print( - ogl.get_branch("master") - ) # gets a json of information about the master branch of current project. -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/hdf.html b/docs/_modules/py2store/ext/hdf.html deleted file mode 100644 index 19911c8..0000000 --- a/docs/_modules/py2store/ext/hdf.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - - py2store.ext.hdf — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.hdf

-from io import BytesIO
-from h5py import File
-from h5py.h5r import get_name
-import numpy as np
-from py2store import KvReader
-
-
-
[docs]class HdfFileReader(KvReader): - def __init__(self, src): - if isinstance(src, bytes): - src = BytesIO(src) - self._src = File(src, "r") - - def __iter__(self): - return self._src.__iter__() - - def __getitem__(self, k): - return HdfDatasetReader(self._src.__getitem__(k), self._src)
- - -
[docs]class HdfDatasetReader(KvReader): - def __init__(self, src, root=None): - self._src = src - self._root = root - - def __iter__(self): - return self._src.__iter__() - - def __getitem__(self, k): - return HdfRefReader(self._src.__getitem__(k), self._root)
- - -
[docs]class HdfRefReader(KvReader): - def __init__(self, src, root): - self._src = src - self._root = root - - def __iter__(self): - return (get_name(ref, self._root.id) for ref in self._src) - - def __getitem__(self, k): - return np.array(self._root[k])
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/kaggle.html b/docs/_modules/py2store/ext/kaggle.html deleted file mode 100644 index 1a870e1..0000000 --- a/docs/_modules/py2store/ext/kaggle.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - - py2store.ext.kaggle — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.kaggle

-from kaggle.api import KaggleApi
-from py2store import KvReader, FilesOfZip
-from py2store.util import lazyprop
-
-DFLT_MAX_ITEMS = 200
-DFLT_MAX_PAGES = 10
-
-
-class DataInfoPaggedItems:
-    def __init__(self):
-        self.pages = []
-        self.ref_to_idx = dict()
-
-    def append(self, page_contents):
-        # TODO: Atomize this code so it can't be broken by async
-        page = self.n_pages()
-        for i, v in enumerate(page_contents):
-            ref = v.get("ref", None)
-            if ref is not None:
-                self.ref_to_idx[ref] = (page, i)
-        self.pages.append(page_contents)
-
-    def __getitem__(self, ref):
-        page, i = self.ref_to_idx[ref]
-        return self.pages[page][i]
-
-    def __len__(self):
-        return len(self.ref_to_idx)
-
-    def get_page_contents(self, page):
-        return self.pages[page]
-
-    def n_pages(self):
-        return len(self.pages)
-
-
-
[docs]class KaggleDatasetInfoReader(KvReader): - """ - A KvReader to access Kaggle resources. - - Prerequisites: - pip install kaggle - Having a kaggle api token where the kaggle library will be looking for it - (see https://github.com/Kaggle/kaggle-api#api-credentials) - - You seed a Reader by specifying any combination of things like search terms, groups, filetypes etc. - See `kaggle.api` for more information. - Additional (and specific to py2store reader) arguments are - start_page (default 0, but useful for paging), - max_n_pages (to not hit the API too hard if your query is large), and - warn_if_there_are_more_items if you want to be warned if there's more items than what you see - - >>> ka = KaggleDatasetInfoReader(search='coronavirus covid') - >>> ka = KaggleDatasetInfoReader(user='sudalairajkumar', start_page=1, max_n_pages=2) - - """ - - def __init__( - self, - *, - group=None, - sort_by=None, - filetype=None, - license=None, - tagids=None, - search=None, - user=None, - start_page=0, - max_n_pages=DFLT_MAX_PAGES, - warn_if_there_are_more_items=False, - **kwargs, - ): - """ - :param async_req bool - :param str group: Display datasets by a particular group - :param str sort_by: Sort the results - :param str filetype: Display datasets of a specific file type - :param str license: Display datasets with a specific license - :param str tagids: A comma separated list of tags to filter by - :param str search: Search terms - :param str user: Display datasets by a specific user or organization - :param int start_page: Page to start at (default is 0) - :param int max_n_pages: Maximum number of pages the container should hold (to avoid API overuse) - :param bool warn_if_there_are_more_items: To be warned if there's more items than what you see - :param int max_size: Max Dataset Size (bytes) - :param int min_size: Max Dataset Size (bytes) - :param kwargs: - """ - explicit_fields = { - "group", - "sort_by", - "filetype", - "license", - "tagids", - "search", - "user", - } - locs = locals() - kwargs.update( - **{k: locs[k] for k in explicit_fields if locs[k] is not None} - ) - self.dataset_filt = kwargs - self._source = KaggleApi() - self._source.authenticate() - self.start_page = start_page - self.max_n_pages = max_n_pages - self.warn_if_there_are_more_items = warn_if_there_are_more_items - self.last_page = None - self.max_pages_reached = None - - def _info_items_gen(self): - page_num = self.start_page - while (page_num - self.start_page) < self.max_n_pages: - new_page_contents = self._source.datasets_list( - page=page_num, **self.dataset_filt - ) - if len(new_page_contents) > 0: - yield from new_page_contents - page_num += 1 - else: - self.max_pages_reached = True - break - self.last_page = page_num - - @lazyprop - def info_of_ref(self): - return {item["ref"]: item for item in self.cached_info_items} - - @lazyprop - def cached_info_items(self): - return list(self._info_items_gen()) - - def __iter__(self): - yield from self.info_of_ref - - def __getitem__(self, k): - """ - Get information (a dict) about a dataset, given its ref (a 'user_slug/dataset_slug' string). - Note: Allows to access all valid references. Not just those within the current container. - """ - return self.info_of_ref[k] - - def __len__(self): - n = len(self.info_of_ref) - self._warn_reached_max(n) - return n - - def _warn_reached_max(self, n): - if self.max_pages_reached and self.warn_if_there_are_more_items: - from warnings import warn - - warn( - f"The container has {n} items, but the max number of pages" - f"({self.max_n_pages}) was reached, so there may be more on kaggle than what you see! " - "If you want more, set max_items to something higher (but beware of overusing your API rights)" - )
- - -# TODO: Make it less wasteful to get from a KaggleDatasetInfoReader to KaggleDatasetReader. -# For example, by having a from_info_reader classmethod constructor, or putting both together. -
[docs]class KaggleBinaryDatasetReader(KaggleDatasetInfoReader): - def __getitem__(self, k): - """ - Download a dataset, given its ref (a 'user_slug/dataset_slug' string). - Will return the binary of the dataset, which should be saved to a file to be persisted. - Note: Allows to access all valid references. Not just those within the current container. - """ - owner_slug, dataset_slug = k.split("/") - response = self._source.process_response( - self._source.datasets_download_with_http_info( - owner_slug=owner_slug, - dataset_slug=dataset_slug, - _preload_content=False, - ) - ) - return response.read()
- - -
[docs]class KaggleDatasetReader(KaggleBinaryDatasetReader): - def __getitem__(self, k): - """ - Download a dataset, given its ref (a 'user_slug/dataset_slug' string). - Will return the binary of the dataset, which should be saved to a file to be persisted. - Note: Allows to access all valid references. Not just those within the current container. - """ - return FilesOfZip(super().__getitem__(k))
- -# -# dataset = 'sudalairajkumar/novel-corona-virus-2019-dataset' -# -# owner_slug, dataset_slug = dataset.split('/') -# # path=None, -# # force=False, -# # quiet=True, -# # unzip=False): -# """ download all files for a dataset -# -# Parameters -# ========== -# dataset: the string identified of the dataset -# should be in format [owner]/[dataset-name] -# path: the path to download the dataset to -# force: force the download if the file already exists (default False) -# quiet: suppress verbose output (default is True) -# unzip: if True, unzip files upon download (default is False) -# """ -# -# # if path is None: -# # effective_path = self.get_default_download_dir( -# # 'datasets', owner_slug, dataset_slug) -# # else: -# # effective_path = path -# -# response = self.process_response( -# self.datasets_download_with_http_info(owner_slug=owner_slug, -# dataset_slug=dataset_slug, -# _preload_content=False)) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/matlab.html b/docs/_modules/py2store/ext/matlab.html deleted file mode 100644 index 9eb20a0..0000000 --- a/docs/_modules/py2store/ext/matlab.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - - - py2store.ext.matlab — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.matlab

-from io import BytesIO
-from py2store.ext.hdf import HdfFileReader, HdfDatasetReader, HdfRefReader
-
-
-
[docs]def read_matlab_bytes_with_scipy(b: bytes): - """Note: Doesn't work after matlab 7.3. For >= 7.3, use hdf.""" - from scipy.io import loadmat - - return loadmat(BytesIO(b))
- - -def read_matlab_bytes_with_h5py(b: bytes): - import h5py - - return h5py.File(BytesIO(b), "r") -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/module_imports.html b/docs/_modules/py2store/ext/module_imports.html deleted file mode 100644 index 7d00804..0000000 --- a/docs/_modules/py2store/ext/module_imports.html +++ /dev/null @@ -1,409 +0,0 @@ - - - - - - - - py2store.ext.module_imports — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.module_imports

-import os
-from types import ModuleType
-from functools import wraps
-from importlib import import_module
-import warnings
-
-try:
-    from findimports import ModuleGraph
-except ModuleNotFoundError:
-    from ut.util.code.findimports import ModuleGraph
-except ModuleNotFoundError:
-    raise ModuleNotFoundError(
-        "You'll need the findimports module for that! Try `pip install findimports`"
-    )
-
-from py2store import Collection, KvReader, lazyprop, wrap_kvs
-
-
-
[docs]class MyModuleGraph(ModuleGraph): - def __init__( - self, - root, - trackUnusedNames=False, - all_unused=False, - external_dependencies=True, - warn_about_duplicates=False, - verbose=False, - ignore_parse_path_warnings=False, - ): - super().__init__() - self._root = root - if isinstance(root, str) and not os.path.exists(root): - root = import_module(root) - if isinstance(root, ModuleType): - root = root.__file__ - if root.endswith("__init__.py"): - root = os.path.dirname(root) - assert isinstance(root, str) and os.path.exists(root) - self._rootpath = root - - self.trackUnusedNames = trackUnusedNames - self.all_unused = all_unused - self.external_dependencies = external_dependencies - self.warn_about_duplicates = warn_about_duplicates - self.verbose = verbose - - # TODO: This doesn't work. Warnings still showing. Repair! - if ignore_parse_path_warnings: - with warnings.catch_warnings(): - warnings.filterwarnings("ignore") - self.parsePathname(self._rootpath) - else: - self.parsePathname(self._rootpath)
- - -
[docs]class ModulesColl(Collection): - @wraps(MyModuleGraph.__init__) - def __init__(self, *args, **kwargs): - self._source = MyModuleGraph(*args, **kwargs) - - @lazyprop - def _modules(self): - return { - module: module.modname for module in self._source.listModules() - } - - @lazyprop - def _modobj_of_modname(self): - return {modname: module for module, modname in self._modules.items()} - - def __len__(self): - return len(self._modules) - - def __contains__(self, k): - return k in self._modules - - def __iter__(self): - for module in self._modules: - yield module
- - -
[docs]class ModuleImportsBase(KvReader, ModulesColl): - @staticmethod - def _key_to_val(k): - return k.imported_names - - def __getitem__(self, k): - return self._key_to_val(k)
- - -def modobj_to_modname(self, modobj): - return self.store._modules[modobj] - - -def modname_to_modobj(self, modname): - return self.store._modobj_of_modname[modname] - - -
[docs]@wrap_kvs( - name="ModuleImports", - key_of_id=modobj_to_modname, - id_of_key=modname_to_modobj, - __module__=__name__, -) -class ModuleImports(ModuleImportsBase): - @staticmethod - def _key_to_val(k): - return k.imports - - def print_kvs(self): - for k, v in self.items(): - print(f"{k}" + "\n" + "\n".join(" " + x for x in v))
- - -# A few useful applications ##################################################################################### - -standard_lib_dir = os.path.dirname(os.__file__) - - -
[docs]def standard_lib_names_gen(include_underscored=True): - """ - Generates names of standard libs from python environment it was called from. - - :param include_underscored: Whether to include names that start with underscore or not. - - - >>> standard_lib_names = set(standard_lib_names_gen(include_underscored=True)) - >>> # verify that a few known libs are there (including three folders and three py files) - >>> assert {'collections', 'asyncio', 'os', 'dis', '__future__'}.issubset(standard_lib_names) - >>> # verify that other decoys are not in there - >>> assert {'__pycache__', 'LICENSE.txt', 'config-3.8-darwin', '.DS_Store'}.isdisjoint(standard_lib_names) - """ - import os - - yield from { - "itertools", - "sys", - } # exceptions that don't have a .py or package - for filename in os.listdir(standard_lib_dir): - if not include_underscored and filename.startswith("_"): - continue - if filename == "site-packages": - continue - filepath = os.path.join(standard_lib_dir, filename) - name, ext = os.path.splitext(filename) - if filename.endswith(".py") and os.path.isfile(filepath): - if str.isidentifier(name): - yield name - elif os.path.isdir(filepath) and "__init__.py" in os.listdir(filepath): - yield name
- - -standard_lib_names_gen.standard_lib_dir = standard_lib_dir - -standard_lib_names = set(standard_lib_names_gen(include_underscored=True)) - -import builtins - -builtin_names = set(dir(builtins)) - -python_names = builtin_names | standard_lib_names - - -
[docs]def imports_for(root, post=set): - """ - - :param root: - :param post: Postprocess iterable. For example: - `set`, when order and repetition doesn't matter - `collections.Counter`, to count number of modules where the module is imported, - `lambda module: set(x.split('.')[0] for x in module)` if you only care about the top level package - :return: - - >>> import wave - >>> assert imports_for(wave) == {'warnings', 'builtins', 'sys', 'audioop', 'chunk', 'struct', 'collections'} - """ - import itertools - - m = ModuleImports(root) - imports_gen = itertools.chain.from_iterable(tuple(v) for v in m.values()) - if callable(post): - return post(imports_gen) - else: - return imports_gen
- - -from functools import partial -from collections import Counter - -imports_for.set = partial(imports_for, post=set) -imports_for.counter = partial(imports_for, post=Counter) -imports_for.most_common = partial( - imports_for, post=lambda x: Counter(x).most_common() -) -imports_for.first_level = partial( - imports_for, post=lambda x: set(xx.split(".")[0] for xx in x) -) -imports_for.first_level_count = partial( - imports_for, post=lambda x: Counter(xx.split(".")[0] for xx in x) -) -imports_for.third_party = partial( - imports_for, - post=lambda module: set( - xx.split(".")[0] - for xx in module - if xx.split(".")[0] not in standard_lib_names - ), -) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/ext/wordnet.html b/docs/_modules/py2store/ext/wordnet.html deleted file mode 100644 index 429afb8..0000000 --- a/docs/_modules/py2store/ext/wordnet.html +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - - - py2store.ext.wordnet — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.ext.wordnet

-"""The py2store wrapper to nltk.corpus.wordnet. Your no fuss gateway to (English) words.
-
-The easiest way to get nltk.corpus.wordnet is
-
-```
-pip install nltk
-```
-in your terminal, and then in a python console:
-
-```
->>> import nltk; nltk.download('wordnet')  # doctest: +SKIP
-```
-
-If you don't like that way, [see here](https://www.nltk.org/install.html) for other ways to get wordnet.
-
-The central construct of this module is the Synset (a set of synonyms that share a common meaning).
-To see a few things you can do with Synsets, naked, [see here](https://www.nltk.org/howto/wordnet.html).
-
-Here we put a py2store wrapper around this stuff.
-
-What is WordNet? https://wordnet.princeton.edu/
-
-"""
-import re
-
-from i2.signatures import Sig
-
-from py2store.mixins import ReadOnlyMixin
-from py2store.util import ModuleNotFoundErrorNiceMessage
-from py2store.sources import Attrs
-from py2store import Store, KvReader, cached_keys, add_ipython_key_completions
-
-with ModuleNotFoundErrorNiceMessage("""You don't seem to have nltk.corpus.wordnet. 
-If you don't have nltk already, do ``pip install nltk``, and if it's that 
-you don't have wordnet downloaded, do ``import nltk; nltk.download('wordnet');``
-"""):
-    from nltk.corpus import wordnet as wn
-    from nltk.corpus.reader.wordnet import Synset, Lemma
-
-
-
[docs]def callable_attr_names(obj): - """Set of attributes that are not callable""" - return {name for name, a in Attrs(obj).items() if not callable(a.src)}
- - -
[docs]def fully_defaulted_method_names(obj): - """Set of method names whose arguments all have defaults (so are callable without arguments).""" - return { - name for name, a in Attrs(obj).items() - if callable(a.src) and len(Sig(a.src)) - len(Sig(a.src).defaults) == 1}
- - -
[docs]def keyable_attr_names(obj): - """Set of attribute names of object that can be used as keys (because not callable, or callable without args).""" - return callable_attr_names(obj) | fully_defaulted_method_names(obj)
- - -# lemma_names_of_non_callable_attrs = callable_attr_names(Lemma) -# lemma_fully_defaulted_method_names = fully_defaulted_method_names(Lemma) -# -# synset_names_of_non_callable_attrs = callable_attr_names(Synset) -# synset_fully_defaulted_method_names = fully_defaulted_method_names(Synset) - -# kv_synset_key_names = synset_names_of_non_callable_attrs | synset_fully_defaulted_method_names - - -def wordnet_element_store_base(element_cls, __module__=__name__): - @add_ipython_key_completions - @cached_keys(keys_cache=keyable_attr_names(element_cls), __module__=__module__) - class _WordnetElementStore(Store): - _from_name = None - - # def __new__(cls, *args, **kwargs): - # if len(args) > 0 and isinstance(args[0], str): - # return cls.from_name(args[0]) - # else: - # return super().__new__(*args, **kwargs) - - @classmethod - def from_name(cls, name): - return cls(cls._from_name(name)) - - def dict_of_non_empty_values(self): - return {k: v for k, v in self.items() if not isinstance(v, list) or len(v) > 0} - - def __getitem__(self, k): - attr = getattr(self, k) - if callable(attr): - attr = attr() - return attr - - def __repr__(self): - return f"{self.__class__.__name__}('{self._name}')" - - _WordnetElementStore._from_name = {Lemma: wn.lemma, Synset: wn.synset}.get(element_cls, None) - - return _WordnetElementStore - - -# Parsing method from nltk.corpus.reader.wordnet.Lemma's documentation. -# Documentation states "These methods all return lists of Lemmas" -lemma_methods_returning_lemmas = set(re.findall("\w+", """ - - antonyms - - hypernyms, instance_hypernyms - - hyponyms, instance_hyponyms - - member_holonyms, substance_holonyms, part_holonyms - - member_meronyms, substance_meronyms, part_meronyms - - topic_domains, region_domains, usage_domains - - attributes - - derivationally_related_forms - - entailments - - causes - - also_sees - - verb_groups - - similar_tos - - pertainyms -""")) - - -
[docs]class KvLemma(wordnet_element_store_base(Lemma)): - """ - >>> lm = KvLemma.from_name('vocal.a.01.vocal') - >>> assert len(lm) == len(dict(lm)) == 33 - >>> sorted(lm.dict_of_non_empty_values().items()) #doctest: +NORMALIZE_WHITESPACE - [('antonyms', [KvLemma('instrumental.a.01.instrumental')]), - ('count', 1), - ('derivationally_related_forms', [KvLemma('vocalize.v.02.vocalize')]), - ('key', 'vocal%3:01:02::'), - ('lang', 'eng'), - ('name', 'vocal'), - ('pertainyms', [KvLemma('voice.n.02.voice')]), - ('synset', KvSynset('vocal.a.01')), - ('syntactic_marker', None)] - """ - - def __getitem__(self, k): - attr = super().__getitem__(k) - if k in lemma_methods_returning_lemmas: - return list(map(KvLemma, attr)) - elif k == 'synset': - return KvSynset(attr) - else: - return attr - - def __repr__(self): - tup = type(self).__name__, self._synset._name, self._name - return "%s('%s.%s')" % tup
- - -# Parsing method from nltk.corpus.reader.wordnet.Synset's documentation. -# Documentation states "These methods all return lists of Synsets" -synset_methods_returning_synsets = set(re.findall("\w+", """ - - hypernyms, instance_hypernyms - - hyponyms, instance_hyponyms - - member_holonyms, substance_holonyms, part_holonyms - - member_meronyms, substance_meronyms, part_meronyms - - attributes - - entailments - - causes - - also_sees - - verb_groups - - similar_tos -""")) - -synset_methods_returning_list_of_lists_of_synsets = {'hypernym_paths', 'hyponym_paths'} - - -# @add_ipython_key_completions -# @cached_keys(keys_cache=keyable_attr_names(Synset), __module__=__name__) -
[docs]class KvSynset(wordnet_element_store_base(Synset)): - """A thin layer on top of nltk.corpus.reader.wordnet.Synset that will give us a dict-like interface of it. - - Think of "synset" as a "concept". - For a list (or rather "dict") of these, checkout ``py2store.ext.wordnet.Synsets`` - - >>> ss = KvSynset.from_name('sound.n.01') - >>> ss - KvSynset('sound.n.01') - - ``ss`` is a Mapping (meaning "dict-like") - - >>> from typing import Mapping - >>> assert isinstance(ss, Mapping) - - So let's list it's keys: - - >>> print(*sorted(ss)) #doctest: +NORMALIZE_WHITESPACE - also_sees attributes causes definition entailments examples frame_ids hypernym_distances hypernym_paths hypernyms - hyponyms in_region_domains in_topic_domains in_usage_domains instance_hypernyms instance_hyponyms lemma_names - lemmas lexname max_depth member_holonyms member_meronyms min_depth name offset part_holonyms part_meronyms pos - region_domains root_hypernyms similar_tos substance_holonyms substance_meronyms topic_domains usage_domains - verb_groups - - So lots of info about that concept. - - >>> ss['definition'] - 'the particular auditory effect produced by a given cause' - >>> ss['examples'] - ['the sound of rain on the roof', 'the beautiful sound of music'] - >>> ss['lemma_names'] # a.k.a. "words used for that concept" - ['sound'] - - - Retrieve everything at once: - - >>> d = dict(ss) - >>> len(d.keys() & {'definition', 'also_sees', 'hypernyms'}) - 3 - - Display items for non-empty values - >>> for k, v in sorted(ss.items()): # doctest: +SKIP - ... if v: - ... print(f"{k}: {v}") - definition: the particular auditory effect produced by a given cause - examples: ['the sound of rain on the roof', 'the beautiful sound of music'] - hypernym_distances: {(KvSynset('attribute.n.02'), 3), (KvSynset('sound_property.n.01'), 1), - ... (KvSynset('property.n.02'), 2), (KvSynset('entity.n.01'), 5), (KvSynset('sound.n.01'), 0), - ... (KvSynset('abstraction.n.06'), 4)} - hypernym_paths: [[KvSynset('entity.n.01'), KvSynset('abstraction.n.06'), KvSynset('attribute.n.02'), - ... KvSynset('property.n.02'), KvSynset('sound_property.n.01'), KvSynset('sound.n.01')]] - hypernyms: [KvSynset('sound_property.n.01')] - hyponyms: [KvSynset('noisiness.n.01'), KvSynset('ring.n.01'), KvSynset('unison.n.03'), KvSynset('voice.n.01')] - lemma_names: ['sound'] - lemmas: [KvLemma('sound.n.01.sound')] - lexname: noun.attribute - max_depth: 5 - min_depth: 5 - name: sound.n.01 - offset: 4981139 - pos: n - root_hypernyms: [Synset('entity.n.01')] - """ - - def __getitem__(self, k): - attr = super().__getitem__(k) - if k in synset_methods_returning_synsets: - return list(map(KvSynset, attr)) - elif k in synset_methods_returning_list_of_lists_of_synsets: - return [list(map(KvSynset, x)) for x in attr] - elif k == 'hypernym_distances': - return {(KvSynset(ss), dist) for ss, dist in attr} - elif k == 'lemmas': - return [KvLemma(lemma) for lemma in attr] - else: - return attr
- - -
[docs]@add_ipython_key_completions -class Synsets(ReadOnlyMixin, Store): - """ - >>> s = Synsets() - >>> len(s) - 117659 - >>> list(s)[:5] - ['able.a.01', 'unable.a.01', 'abaxial.a.01', 'adaxial.a.01', 'acroscopic.a.01'] - >>> ss = s['sound.n.01'] - >>> ss - KvSynset('sound.n.01') - - And what can you do with a KvSynset? - Well, you'll just have to check out it's documentation! - - """ - - def __init__(self, pos=None): - super().__init__(store={ss.name(): ss for ss in wn.all_synsets(pos=pos)}) - - def __getitem__(self, k): - return KvSynset(self.store[k]) - - def __repr__(self): - return f"{self.__class__.__name__}()"
- - -
[docs]@cached_keys(keys_cache=set) -class Lemmas(KvReader): - def __iter__(self): - return wn.all_lemma_names() - - def __getitem__(self, k): - return {ss.name(): KvSynset(ss) for ss in wn.synsets(k)}
- - -import numpy as np -import io -import pandas as pd -import itertools -from collections import Counter - - -def print_word_definitions(word): - print(word_definitions_string(word)) - - -def word_definitions_string(word): - return '\n'.join(['%d: %s (%s)' - % (i, x.definition(), x.name()) for i, x in enumerate(wn.synsets(word))]) - - -def print_word_lemmas(word): - t = Counter([l.name for s in wn.synsets(word) for l in s.lemmas]) - print(pd.Series(index=list(t.keys()), data=list(t.values())).sort(inplace=False, ascending=False)) - - -def _lemma_names_str(syn): - return '(' + ', '.join(syn.lemma_names) + ')' - - -def print_hypos_with_synset(syn, tab=''): - print(tab + syn.name) - h = syn.hyponyms() - if len(h) > 0: - for hi in h: - print_hypos_with_synset(hi, tab + ' ') - else: - print(tab + ' ' + _lemma_names_str(syn)) - - -def pprint_hypos(syn, tab=''): - print(tab + _lemma_names_str(syn)) - h = syn.hyponyms() - if len(h) > 0: - for hi in h: - pprint_hypos(hi, tab + ' ') - - -class iTree(object): - def __init__(self, value=None): - self.value = value - self.children = [] - self.default_node_2_str = lambda node: str(node.value) - - def __iter__(self): - for v in itertools.chain(*map(iter, self.children)): - yield v - yield self - - def tree_info_str(self, - node_2_str=None, # default info is node value - tab_str=2 * ' ', # tab string - depth=0 - ): - node_2_str = node_2_str or self.default_node_2_str - s = depth * tab_str + node_2_str(self) + '\n' - new_depth = depth + 1 - for child in self.children: - s += child.tree_info_str(node_2_str, tab_str, new_depth) - return s - - -class HyponymTree(iTree): - def __init__(self, value=None): - if isinstance(value, str): - value = wn.synset(value) - super(HyponymTree, self).__init__(value=value) - for hypo in value.hyponyms(): - self.children.append(HyponymTree(hypo)) - self.set_default_node_2_str('name') - - def __str__(self): - return self.value.name() - - def __repr__(self): - return self.value.name() - - def print_lemmas(self, tab=''): - print(tab + _lemma_names_str(self.value)) - for c in self.children: - pprint_hypos(c, tab + ' ') - - def leafs(self): - return [x for x in self] - - @classmethod - def of_hyponyms(cls, syn): - tree = cls(syn) - for hypo in syn.hyponyms(): - tree.children.append(cls.of_hyponyms(hypo)) - return tree - - @staticmethod - def get_node_2_str_function(method='name', **kwargs): - """ - returns a node_2_str function (given it's name) - method could be - * 'name': The synset name (example sound.n.01) - * 'lemma_names': A parenthesized list of lemma names - * 'name_and_def': The synset name and it's definition - * 'lemmas_and_def': The lemma names and definition - """ - if method == 'name': - return lambda node: \ - node.value.name - elif method == 'lemma_names' or method == 'lemmas': - lemma_sep = kwargs.get('lemma_sep', ', ') - return lambda node: \ - '(' + lemma_sep.join(node.value.lemma_names) + ')' - elif method == 'name_and_def': - return lambda node: \ - node.value.name + ': ' + node.value.definition - elif method == 'lemmas_and_def': - lemma_sep = kwargs.get('lemma_sep', ', ') - def_sep = kwargs.get('def_sep', ': ') - return lambda node: \ - '(' + lemma_sep.join(node.value.lemma_names) + ')' \ - + def_sep + node.value.definition - elif method == 'all': - lemma_sep = kwargs.get('lemma_sep', ', ') - def_sep = kwargs.get('def_sep', ': ') - return lambda node: \ - '(' + lemma_sep.join(node.value.lemma_names) + ')' \ - + def_sep + node.value.name \ - + def_sep + node.value.definition - else: - raise ValueError("Unknown node_2_str_function method") - - def set_default_node_2_str(self, method='name'): - """ - will set the default string representation of a synset - (used as a default ny the tree_info_str function for example) - from the name of the method to use - (see get_node_2_str_function(method)) - method could be - * 'name': The synset name (example sound.n.01) - * 'lemma_names': A parenthesized list of lemma names - * 'name_and_def': The synset name and it's definition - * 'lemmas_and_def': The lemma names and definition - """ - self.default_node_2_str = HyponymTree.get_node_2_str_function(method) - - def _df_for_excel_export(self, method='all', method_args={}): - method_args['def_sep'] = ':' - method_args['tab_str'] = method_args.get('tab_str', '* ') - s = '' - # s = 'lemmas' + method_args['def_sep'] + 'synset' + method_args['def_sep'] + 'definition' + '\n' - s += self.tree_info_str(node_2_str=self.get_node_2_str_function(method=method), - tab_str=method_args['tab_str']) - return pd.DataFrame.from_csv(io.StringIO(str(s)), - sep=method_args['def_sep'], header=None, index_col=None) - - def export_info_to_excel(self, filepath, sheet_name='hyponyms', method='all', method_args={}): - d = self._df_for_excel_export(method=method, method_args=method_args) - d.to_excel(filepath, sheet_name=sheet_name, header=False, index=False) - - -class HyponymForest(object): - def __init__(self, tree_list): - assert len(tree_list) == len(set(tree_list)), "synsets in list must be unique" - for i, ss in enumerate(tree_list): - if not isinstance(ss, HyponymTree): - tree_list[i] = HyponymTree(ss) - self.tree_list = tree_list - - def leafs(self): - return {xx for x in self.tree_list for xx in x.leafs()} - - def export_info_to_excel(self, filepath, sheet_name='hyponyms', method='all', method_args={}): - d = pd.DataFrame() - for dd in self.tree_list: - d = pd.concat([d, dd._df_for_excel_export(method=method, method_args=method_args)]) - d.to_excel(filepath, sheet_name=sheet_name, header=False, index=False) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/filesys.html b/docs/_modules/py2store/filesys.html deleted file mode 100644 index 4415bef..0000000 --- a/docs/_modules/py2store/filesys.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - py2store.filesys — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.filesys

-import os
-from os import stat as os_stat
-from functools import wraps
-
-from py2store.base import Collection, KvReader, KvPersister
-from py2store.key_mappers.naming import (
-    mk_pattern_from_template_and_format_dict,
-)
-from py2store.key_mappers.paths import mk_relative_path_store
-from py2store.persisters.local_files import (
-    inf,
-    ensure_slash_suffix,
-    iter_filepaths_in_folder_recursively,
-    iter_dirpaths_in_folder_recursively,
-)
-
-
-def mk_tmp_py2store_dir(dirname=""):
-    from tempfile import gettempdir
-
-    tmpdir = os.path.join(gettempdir(), dirname)
-    os.path.isdir(tmpdir) or os.mkdir(
-        tmpdir
-    )  # make the directory if it doesn't exist
-    return tmpdir
-
-
-def mk_absolute_path(path_format):
-    if path_format.startswith("~"):
-        path_format = os.path.expanduser(path_format)
-    elif path_format.startswith("."):
-        path_format = os.path.abspath(path_format)
-    return path_format
-
-
-# TODO: subpath: Need to be able to allow named and unnamed file format markers (i.e {} and {named})
-
-_dflt_not_valid_error_msg = "Key not valid (usually because does not exist or access not permitted): {}"
-_dflt_not_found_error_msg = "Key not found: {}"
-
-
-
[docs]class KeyValidationError(KeyError): - pass
- - -# TODO: The validate and try/except is a frequent pattern. Make it a decorator. -def validate_key_and_raise_key_error_on_exception(func): - @wraps(func) - def wrapped_method(self, k, *args, **kwargs): - self.validate_key(k) - try: - return func(self, k, *args, **kwargs) - except Exception as e: - raise KeyError(e) - - return wrapped_method - - -
[docs]class FileSysCollection(Collection): - # rootdir = None # mentioning here so that the attribute is seen as an attribute before instantiation. - - def __init__( - self, rootdir, subpath="", pattern_for_field=None, max_levels=None - ): - if max_levels is None: - max_levels = inf - subpath_implied_min_levels = len(subpath.split(os.path.sep)) - 1 - assert ( - max_levels >= subpath_implied_min_levels - ), f"max_levels is {max_levels}, but subpath {subpath} would imply at least {subpath_implied_min_levels}" - pattern_for_field = pattern_for_field or {} - self.rootdir = ensure_slash_suffix(rootdir) - self.subpath = subpath - self._key_pattern = mk_pattern_from_template_and_format_dict( - os.path.join(rootdir, subpath), pattern_for_field - ) - self._max_levels = max_levels - - def is_valid_key(self, k): - return bool(self._key_pattern.match(k)) - - def validate_key( - self, - k, - err_msg_format=_dflt_not_valid_error_msg, - err_type=KeyValidationError, - ): - if not self.is_valid_key(k): - raise err_type(err_msg_format.format(k))
- - -
[docs]class DirCollection(FileSysCollection): - def __iter__(self): - yield from filter( - self.is_valid_key, - iter_dirpaths_in_folder_recursively( - self.rootdir, max_levels=self._max_levels - ), - ) - - def __contains__(self, k): - return self.is_valid_key(k) and os.path.isdir(k)
- - -
[docs]class FileCollection(FileSysCollection): - def __iter__(self): - """ - Iterator of valid filepaths. - >>> import os - >>> filepath = __file__ # path to this module - >>> dirpath = os.path.dirname(__file__) # path of the directory where I (the module file) am - >>> s = FileCollection(dirpath, max_levels=0) - >>> - >>> files_in_this_dir = list(s) - >>> filepath in files_in_this_dir - True - """ - yield from filter( - self.is_valid_key, - iter_filepaths_in_folder_recursively( - self.rootdir, max_levels=self._max_levels - ), - ) - - def __contains__(self, k): - """ - Checks if k is valid and contained in the store - >>> import os - >>> filepath = __file__ # path to this module - >>> dirpath = os.path.dirname(__file__) # path of the directory where I (the module file) am - >>> s = FileCollection(dirpath, max_levels=0) - >>> - >>> filepath in s - True - >>> '_this_filepath_will_never_be_valid_' in s - False - """ - return self.is_valid_key(k) and os.path.isfile(k)
- - -
[docs]class FileInfoReader(FileCollection, KvReader): - def __getitem__(self, k): - self.validate_key(k) - return os_stat(k)
- - -
[docs]class FileBytesReader(FileCollection, KvReader): - _read_open_kwargs = dict( - mode="rb", - buffering=-1, - encoding=None, - errors=None, - newline=None, - closefd=True, - opener=None, - ) - - @validate_key_and_raise_key_error_on_exception - def __getitem__(self, k): - """ - Gets the bytes contents of the file k. - >>> import os - >>> filepath = __file__ - >>> dirpath = os.path.dirname(__file__) # path of the directory where I (the module file) am - >>> s = FileBytesReader(dirpath, max_levels=0) - >>> - >>> ####### Get the first 9 characters (as bytes) of this module ##################### - >>> s[filepath][:9] - b'import os' - >>> - >>> ####### Test key validation ##################### - >>> s['not_a_valid_key'] # this key is not valid since not under the dirpath folder - Traceback (most recent call last): - ... - filesys.KeyValidationError: 'Key not valid (usually because does not exist or access not permitted): not_a_valid_key' - >>> - >>> ####### Test further exceptions (that should be wrapped in KeyError) ##################### - >>> # this key is valid, since under dirpath, but the file itself doesn't exist (hopefully for this test) - >>> non_existing_file = os.path.join(dirpath, 'non_existing_file') - >>> try: - ... s[non_existing_file] - ... except KeyError: - ... print("KeyError (not FileNotFoundError) was raised.") - KeyError (not FileNotFoundError) was raised. - """ - with open(k, **self._read_open_kwargs) as fp: - return fp.read()
- - -class LocalFileDeleteMixin: - @validate_key_and_raise_key_error_on_exception - def __delitem__(self, k): - os.remove(k) - - -
[docs]class FileBytesPersister(FileBytesReader, KvPersister): - _write_open_kwargs = dict( - mode="wb", - buffering=-1, - encoding=None, - errors=None, - newline=None, - closefd=True, - opener=None, - ) - - @validate_key_and_raise_key_error_on_exception - def __setitem__(self, k, v): - with open(k, **self._write_open_kwargs) as fp: - return fp.write(v) - - @validate_key_and_raise_key_error_on_exception - def __delitem__(self, k): - os.remove(k)
- - -RelPathFileBytesReader = mk_relative_path_store( - FileBytesReader, - prefix_attr="rootdir", - __name__="RelPathFileBytesReader", - __module__=__name__ -) - - -
[docs]class FileStringReader(FileBytesReader): - _read_open_kwargs = dict(FileBytesReader._read_open_kwargs, mode="rt")
- - -
[docs]class FileStringPersister(FileBytesPersister): - _write_open_kwargs = dict(FileBytesPersister._write_open_kwargs, mode="wt")
- - -RelPathFileStringReader = mk_relative_path_store( - FileStringReader, - prefix_attr="rootdir", - __name__="RelPathFileStringReader", -) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/key_mappers/naming.html b/docs/_modules/py2store/key_mappers/naming.html deleted file mode 100644 index 27701c1..0000000 --- a/docs/_modules/py2store/key_mappers/naming.html +++ /dev/null @@ -1,1409 +0,0 @@ - - - - - - - - py2store.key_mappers.naming — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.key_mappers.naming

-"""
-This module is about generating, validating, and operating on (parametrized) fields (i.e. stings, e.g. paths).
-"""
-
-import re
-import os
-from functools import partial, wraps
-from types import MethodType
-
-from py2store.utils.signatures import set_signature_of_func
-from py2store.errors import KeyValidationError, _assert_condition
-
-assert_condition = partial(_assert_condition, err_cls=KeyValidationError)
-
-path_sep = os.path.sep
-
-base_validation_funs = {
-    "be a": isinstance,
-    "be in": lambda val, check_val: val in check_val,
-    "be at least": lambda val, check_val: val >= check_val,
-    "be more than": lambda val, check_val: val > check_val,
-    "be no more than": lambda val, check_val: val <= check_val,
-    "be less than": lambda val, check_val: val < check_val,
-}
-
-dflt_validation_funs = base_validation_funs
-dflt_all_kwargs_should_be_in_validation_dict = False
-dflt_ignore_misunderstood_validation_instructions = False
-
-dflt_arg_pattern = r".+"
-
-day_format = "%Y-%m-%d"
-day_format_pattern = re.compile("\d{4}-\d{2}-\d{2}")
-
-capture_template = "({format})"
-named_capture_template = "(?P<{name}>{format})"
-
-fields_re = re.compile("(?<={)[^}]+(?=})")
-
-
-
[docs]def validate_kwargs( - kwargs_to_validate, - validation_dict, - validation_funs=None, - all_kwargs_should_be_in_validation_dict=False, - ignore_misunderstood_validation_instructions=False, -): - """ - Utility to validate a dict. It's main use is to validate function arguments (expressing the validation checks - in validation_dict) by doing validate_kwargs(locals()), usually in the beginning of the function - (to avoid having more accumulated variables than we need in locals()) - :param kwargs_to_validate: as the name implies... - :param validation_dict: A dict specifying what to validate. Keys are usually name of variables (when feeding - locals()) and values are dicts, themselves specifying check:check_val pairs where check is a string that - points to a function (see validation_funs argument) and check_val is an object that the kwargs_to_validate - value will be checked against. - :param validation_funs: A dict of check:check_function(val, check_val) where check_function is a function returning - True if val is valid (with respect to check_val). - :param all_kwargs_should_be_in_validation_dict: If True, will raise an error if kwargs_to_validate contains - keys that are not in validation_dict. - :param ignore_misunderstood_validation_instructions: If True, will raise an error if validation_dict contains - a key that is not in validation_funs (safer, since if you mistype a key in validation_dict, the function will - tell you so! - :return: True if all the validations passed. - - >>> validation_dict = { - ... 'system': { - ... 'be in': {'darwin', 'linux'} - ... }, - ... 'fv_version': { - ... 'be a': int, - ... 'be at least': 5 - ... } - ... } - >>> validate_kwargs({'system': 'darwin'}, validation_dict) - True - >>> try: - ... validate_kwargs({'system': 'windows'}, validation_dict) - ... except AssertionError as e: - ... assert str(e).startswith('system must be in') # omitting the set because inconsistent order - >>> try: - ... validate_kwargs({'fv_version': 9.9}, validation_dict) - ... except AssertionError as e: - ... print(e) - fv_version must be a <class 'int'> - >>> try: - ... validate_kwargs({'fv_version': 4}, validation_dict) - ... except AssertionError as e: - ... print(e) - fv_version must be at least 5 - >>> validate_kwargs({'fv_version': 6}, validation_dict) - True - """ - validation_funs = dict( - base_validation_funs or {}, **(validation_funs or {}) - ) - for ( - var, - val, - ) in kwargs_to_validate.items(): # for every (var, val) pair of kwargs - if var in validation_dict: # if var is in the validation_dict - for check, check_val in validation_dict[ - var - ].items(): # for every (key, val) of this dict - if ( - check in base_validation_funs - ): # if you have a validation check for it - if not validation_funs[check]( - val, check_val - ): # check it's valid - raise AssertionError( - "{} must {} {}".format(var, check, check_val) - ) # and raise an error if not - elif ( - not ignore_misunderstood_validation_instructions - ): # should ignore if check not understood? - raise AssertionError( - "I don't know what to do with the validation check '{}'".format( - check - ) - ) - elif ( - all_kwargs_should_be_in_validation_dict - ): # should all variables have checks? - raise AssertionError("{} wasn't in the validation_dict") - return True
- - -
[docs]def namedtuple_to_dict(nt): - """ - >>> from collections import namedtuple - >>> NT = namedtuple('MyTuple', ('foo', 'hello')) - >>> nt = NT(1, 42) - >>> nt - MyTuple(foo=1, hello=42) - >>> d = namedtuple_to_dict(nt) - >>> d - {'foo': 1, 'hello': 42} - """ - return {field: getattr(nt, field) for field in nt._fields}
- - -
[docs]def dict_to_namedtuple(d, namedtuple_obj=None): - """ - >>> from collections import namedtuple - >>> NT = namedtuple('MyTuple', ('foo', 'hello')) - >>> nt = NT(1, 42) - >>> nt - MyTuple(foo=1, hello=42) - >>> d = namedtuple_to_dict(nt) - >>> d - {'foo': 1, 'hello': 42} - >>> dict_to_namedtuple(d) - NamedTupleFromDict(foo=1, hello=42) - >>> dict_to_namedtuple(d, nt) - MyTuple(foo=1, hello=42) - """ - if namedtuple_obj is None: - namedtuple_obj = "NamedTupleFromDict" - if isinstance(namedtuple_obj, str): - namedtuple_name = namedtuple_obj - namedtuple_cls = namedtuple(namedtuple_name, tuple(d.keys())) - elif isinstance(namedtuple_obj, tuple) and hasattr( - namedtuple_obj, "_fields" - ): - namedtuple_cls = namedtuple_obj.__class__ - elif isinstance(namedtuple_obj, type): - namedtuple_cls = namedtuple_obj - else: - raise TypeError( - f"Can't resolve the nametuple class specification: {namedtuple_obj}" - ) - - return namedtuple_cls(**d)
- - -
[docs]def update_fields_of_namedtuple( - nt: tuple, *, name_of_output_type=None, remove_fields=(), **kwargs -): - """Replace fields of namedtuple - >>> from collections import namedtuple - >>> NT = namedtuple('NT', ('a', 'b', 'c')) - >>> nt = NT(1,2,3) - >>> nt - NT(a=1, b=2, c=3) - >>> update_fields_of_namedtuple(nt, c=3000) # replacing a single field - NT(a=1, b=2, c=3000) - >>> update_fields_of_namedtuple(nt, c=3000, a=1000) # replacing two fields - NT(a=1000, b=2, c=3000) - >>> update_fields_of_namedtuple(nt, a=1000, c=3000) # see that the original order doesn't change - NT(a=1000, b=2, c=3000) - >>> update_fields_of_namedtuple(nt, b=2000, d='hello') # replacing one field and adding a new one - UpdatedNT(a=1, b=2000, c=3, d='hello') - >>> # Now let's try controlling the name of the output type, remove fields, and add new ones - >>> update_fields_of_namedtuple(nt, name_of_output_type='NewGuy', remove_fields=('a', 'c'), hello='world') - NewGuy(b=2, hello='world') - """ - - output_type_can_be_the_same_as_input_type = (not remove_fields) and set( - kwargs.keys() - ).issubset(nt._fields) - d = dict(namedtuple_to_dict(nt), **kwargs) - for f in remove_fields: - d.pop(f) - - if ( - output_type_can_be_the_same_as_input_type - and name_of_output_type is None - ): - return dict_to_namedtuple(d, nt.__class__) - else: - name_of_output_type = ( - name_of_output_type or f"Updated{nt.__class__.__name__}" - ) - return dict_to_namedtuple(d, name_of_output_type)
- - -empty_field_p = re.compile("{}") - - -
[docs]def get_fields_from_template(template): - """ - Get list from {item} items of template string - :param template: a "template" string (a string with {item} items - -- the kind that is used to mark token for str.format) - :return: a list of the token items of the string, in the order they appear - >>> get_fields_from_template('this{is}an{example}of{a}template') - ['is', 'example', 'a'] - """ - # TODO: Need to use the string module, and need to auto-name the fields instead of refusing unnamed - assert not empty_field_p.search( - template - ), "All fields must be named: That is, no empty {} allowed" - return fields_re.findall(template)
- - -# until_slash = "[^" + path_sep + "]+" -# until_slash_capture = '(' + until_slash + ')' - - -def mk_format_mapping_dict(format_dict, required_keys, sep=path_sep): - until_sep = "[^" + re.escape(sep) + "]+" - new_format_dict = format_dict.copy() - for k in required_keys: - if k not in new_format_dict: - new_format_dict[k] = until_sep - return new_format_dict - - -def mk_capture_patterns(mapping_dict): - new_mapping_dict = dict() - for k, v in mapping_dict.items(): - new_v = capture_template.format(format=v) - new_mapping_dict[k] = new_v - return new_mapping_dict - - -def mk_named_capture_patterns(mapping_dict): - new_mapping_dict = dict() - for k, v in mapping_dict.items(): - new_v = named_capture_template.format(name=k, format=v) - new_mapping_dict[k] = new_v - return new_mapping_dict - - -def template_to_pattern(mapping_dict, template): - if mapping_dict: - p = re.compile( - "{}".format( - "|".join( - [ - "{" + re.escape(x) + "}" - for x in list(mapping_dict.keys()) - ] - ) - ) - ) - return p.sub( - lambda x: mapping_dict[x.string[(x.start() + 1): (x.end() - 1)]], - template, - ) - else: - return template - - -def mk_extract_pattern( - template, format_dict=None, named_capture_patterns=None, name=None -): - format_dict = format_dict or {} - named_capture_patterns = ( - named_capture_patterns or mk_named_capture_patterns(format_dict) - ) - assert name is not None - mapping_dict = dict(format_dict, **{name: named_capture_patterns[name]}) - p = re.compile( - "{}".format( - "|".join( - ["{" + re.escape(x) + "}" for x in list(mapping_dict.keys())] - ) - ) - ) - - return re.compile( - p.sub( - lambda x: mapping_dict[x.string[(x.start() + 1): (x.end() - 1)]], - template, - ) - ) - - -
[docs]def mk_pattern_from_template_and_format_dict(template, format_dict=None): - """Make a compiled regex to match template - - Args: - template: A format string - format_dict: A dict whose keys are template fields and values are regex strings to capture them - - Returns: a compiled regex - - >>> mk_pattern_from_template_and_format_dict('{here}/and/{there}') - re.compile('(?P<here>[^/]+)/and/(?P<there>[^/]+)') - >>> p = mk_pattern_from_template_and_format_dict('{here}/and/{there}', {'there': '\d+'}) - >>> p - re.compile('(?P<here>[^/]+)/and/(?P<there>\\\d+)') - >>> type(p) - <class 're.Pattern'> - >>> p.match('HERE/and/1234').groupdict() - {'here': 'HERE', 'there': '1234'} - """ - format_dict = format_dict or {} - - fields = get_fields_from_template(template) - format_dict = mk_format_mapping_dict(format_dict, fields) - named_capture_patterns = mk_named_capture_patterns(format_dict) - return re.compile(template_to_pattern(named_capture_patterns, template))
- - -def mk_prefix_templates_dicts(template): - fields = get_fields_from_template(template) - prefix_template_dict_including_name = dict() - none_and_fields = [None] + fields - for name in none_and_fields: - if name == fields[-1]: - prefix_template_dict_including_name[name] = template - else: - if name is None: - next_name = fields[0] - else: - next_name = fields[ - 1 - + next( - i for i, _name in enumerate(fields) if _name == name - ) - ] - p = "{" + next_name + "}" - template_idx_of_next_name = re.search(p, template).start() - prefix_template_dict_including_name[name] = template[ - :template_idx_of_next_name - ] - - prefix_template_dict_excluding_name = dict() - for i, name in enumerate(fields): - prefix_template_dict_excluding_name[ - name - ] = prefix_template_dict_including_name[none_and_fields[i]] - prefix_template_dict_excluding_name[None] = template - - return ( - prefix_template_dict_including_name, - prefix_template_dict_excluding_name, - ) - - -
[docs]def mk_kwargs_trans(**trans_func_for_key): - """ Make a dict transformer from functions that depends solely on keys (of the dict to be transformed) - Used to easily make process_kwargs and process_info_dict arguments for LinearNaming. - """ - assert all( - map(callable, trans_func_for_key.values()) - ), "all argument values must be callable" - - def key_based_val_trans(**kwargs): - for k, v in kwargs.items(): - if k in trans_func_for_key: - kwargs[k] = trans_func_for_key[k](v) - return kwargs - - return key_based_val_trans
- - -def _mk(self, *args, **kwargs): - """ - Make a full name with given kwargs. All required name=val must be present (or infered by self.process_kwargs - function. - The required fields are in self.fields. - Does NOT check for validity of the vals. - :param kwargs: The name=val arguments needed to construct a valid name - :return: an name - """ - n = len(args) + len(kwargs) - if n > self.n_fields: - raise ValueError( - f"You have too many arguments: (args, kwargs) is ({args},{kwargs})" - ) - elif n < self.n_fields: - raise ValueError( - f"You have too few arguments: (args, kwargs) is ({args},{kwargs})" - ) - kwargs = dict({k: v for k, v in zip(self.fields, args)}, **kwargs) - if self.process_kwargs is not None: - kwargs = self.process_kwargs(**kwargs) - return self.template.format(**kwargs) - - -class StrTupleDict(object): - def __init__( - self, - template: (str, tuple, list), - format_dict=None, - process_kwargs=None, - process_info_dict=None, - named_tuple_type_name="NamedTuple", - sep: str = path_sep, - ): - """Converting from and to strings, tuples, and dicts. - - Args: - template: The string format template - format_dict: A {field_name: field_value_format_regex, ...} dict - process_kwargs: A function taking the field=value pairs and producing a dict of processed - {field: value,...} dict (where both fields and values could have been processed. - This is useful when we need to process (format, default, etc.) fields, or their values, - according to the other fields of values in the collection. - A specification of {field: function_to_process_this_value,...} wouldn't allow the full powers - we are allowing here. - process_info_dict: A sort of converse of format_dict. - This is a {field_name: field_conversion_func, ...} dict that is used to convert info_dict values - before returning them. - name_separator: Used - - >>> ln = StrTupleDict('/home/{user}/fav/{num}.txt', - ... format_dict={'user': '[^/]+', 'num': '\d+'}, - ... process_info_dict={'num': int}, - ... sep='/' - ... ) - >>> ln.is_valid('/home/USER/fav/123.txt') - True - >>> ln.is_valid('/home/US/ER/fav/123.txt') - False - >>> ln.is_valid('/home/US/ER/fav/not_a_number.txt') - False - >>> ln.mk('USER', num=123) # making a string (with args or kwargs) - '/home/USER/fav/123.txt' - >>> # Note: but ln.mk('USER', num='not_a_number') would fail because num is not valid - >>> ln.info_dict('/home/USER/fav/123.txt') # note in the output, 123 is an int, not a string - {'user': 'USER', 'num': 123} - >>> - >>> # Trying with template given as a tuple, and with different separator - >>> ln = StrTupleDict(template=('first', 'last', 'age'), - ... format_dict={'age': '-*\d+'}, - ... process_info_dict={'age': int}, - ... sep=',') - >>> ln.tuple_to_str(('Thor', "Odinson", 1500)) - 'Thor,Odinson,1500' - >>> ln.str_to_dict('Loki,Laufeyson,1070') - {'first': 'Loki', 'last': 'Laufeyson', 'age': 1070} - >>> ln.str_to_tuple('Odin,Himself,-1') - ('Odin', 'Himself', -1) - >>> ln.tuple_to_dict(('Odin', 'Himself', -1)) - {'first': 'Odin', 'last': 'Himself', 'age': -1} - >>> ln.dict_to_tuple({'first': 'Odin', 'last': 'Himself', 'age': -1}) - ('Odin', 'Himself', -1) - """ - if format_dict is None: - format_dict = {} - - self.sep = sep - - if isinstance(template, str): - self.template = template - else: - self.template = self.sep.join([f"{{{x}}}" for x in template]) - - fields = get_fields_from_template(self.template) - - format_dict = mk_format_mapping_dict(format_dict, fields) - - named_capture_patterns = mk_named_capture_patterns(format_dict) - - pattern = template_to_pattern(named_capture_patterns, self.template) - pattern += "$" - pattern = re.compile(pattern) - - extract_pattern = {} - for name in fields: - extract_pattern[name] = mk_extract_pattern( - self.template, format_dict, named_capture_patterns, name - ) - - if isinstance(process_info_dict, dict): - _processor_for_kw = process_info_dict - - def process_info_dict(**info_dict): - return { - k: _processor_for_kw.get(k, lambda x: x)(v) - for k, v in info_dict.items() - } - - self.fields = fields - self.n_fields = len(fields) - self.format_dict = format_dict - self.named_capture_patterns = named_capture_patterns - self.pattern = pattern - self.extract_pattern = extract_pattern - self.process_kwargs = process_kwargs - self.process_info_dict = process_info_dict - - def _mk(self, *args, **kwargs): - """ - Make a full name with given kwargs. All required name=val must be present (or infered by self.process_kwargs - function. - The required fields are in self.fields. - Does NOT check for validity of the vals. - :param kwargs: The name=val arguments needed to construct a valid name - :return: an name - """ - n = len(args) + len(kwargs) - if n > self.n_fields: - raise ValueError( - f"You have too many arguments: (args, kwargs) is ({args},{kwargs})" - ) - elif n < self.n_fields: - raise ValueError( - f"You have too few arguments: (args, kwargs) is ({args},{kwargs})" - ) - kwargs = dict({k: v for k, v in zip(self.fields, args)}, **kwargs) - if self.process_kwargs is not None: - kwargs = self.process_kwargs(**kwargs) - return self.template.format(**kwargs) - - set_signature_of_func(_mk, ["self"] + self.fields) - self.mk = MethodType(_mk, self) - self.NamedTuple = namedtuple(named_tuple_type_name, self.fields) - - def is_valid(self, s: str): - """Check if the name has the "upload format" (i.e. the kind of fields that are _ids of fv_mgc, and what - name means in most of the iatis system. - :param s: the string to check - :return: True iff name has the upload format - """ - return bool(self.pattern.match(s)) - - def str_to_dict(self, s: str): - """ - Get a dict with the arguments of an name (for example group, user, subuser, etc.) - :param s: - :return: a dict holding the argument fields and values - """ - m = self.pattern.match(s) - if m: - info_dict = m.groupdict() - if self.process_info_dict: - return self.process_info_dict(**info_dict) - else: - return info_dict - else: - raise ValueError(f"Invalid string format: {s}") - - def str_to_tuple(self, s: str): - info_dict = self.str_to_dict(s) - return tuple(info_dict[x] for x in self.fields) - - def str_to_namedtuple(self, s: str): - return self.dict_to_namedtuple(self.str_to_dict(s)) - - def str_to_simple_str(self, s: str): - return self.sep.join(self.str_to_tuple(s)) - - def simple_str_to_str(self, ss: str): - return self.tuple_to_str(ss.split(self.sep)) - - def super_dict_to_str(self, d: dict): - """Like dict_to_str, but the input dict can have extra keys that are not used by dict_to_str""" - return self.mk(**{k: v for k, v in d.items() if k in self.fields}) - - def dict_to_str(self, d: dict): - return self.mk(**d) - - def dict_to_tuple(self, d): - assert_condition( - len(self.fields) == len(d), - f"len(d)={len(d)} but len(fields)={len(self.fields)}", - ) - return tuple(d[f] for f in self.fields) - - def dict_to_namedtuple(self, d): - return self.NamedTuple(**d) - - def tuple_to_dict(self, t): - assert_condition( - len(self.fields) == len(t), - f"len(d)={len(t)} but len(fields)={len(self.fields)}", - ) - return {f: x for f, x in zip(self.fields, t)} - - def tuple_to_str(self, t): - return self.mk(*t) - - def namedtuple_to_tuple(self, nt): - return tuple(nt) - - def namedtuple_to_dict(self, nt): - return {k: getattr(nt, k) for k in self.fields} - - def namedtuple_to_str(self, nt): - return self.dict_to_str(self.namedtuple_to_dict(nt)) - - def extract(self, field, s): - """Extract a single item from an name - :param field: field of the item to extract - :param s: the string from which to extract it - :return: the value for name - """ - return self.extract_pattern[field].match(s).group(1) - - info_dict = str_to_dict # alias - info_tuple = str_to_tuple # alias - - def replace_name_elements(self, s: str, **elements_kwargs): - """Replace specific name argument values with others - :param s: the string to replace - :param elements_kwargs: the arguments to replace (and their values) - :return: a new name - """ - name_info_dict = self.info_dict(s) - for k, v in elements_kwargs.items(): - name_info_dict[k] = v - return self.mk(**name_info_dict) - - def _info_str(self): - kv = self.__dict__.copy() - exclude = [ - "process_kwargs", - "extract_pattern", - "prefix_pattern", - "prefix_template_including_name", - "prefix_template_excluding_name", - ] - for f in exclude: - kv.pop(f) - s = "" - s += " * {}: {}\n".format("template", kv.pop("template")) - s += " * {}: {}\n".format("template", kv.pop("sep")) - s += " * {}: {}\n".format("format_dict", kv.pop("format_dict")) - - for k, v in kv.items(): - if hasattr(v, "pattern"): - v = v.pattern - s += " * {}: {}\n".format(k, v) - - return s - - def _print_info_str(self): - print(self._info_str()) - - -# TODO: mk_prefix has wrong signature. Repair. -
[docs]class StrTupleDictWithPrefix(StrTupleDict): - """Converting from and to strings, tuples, and dicts, but with partial "prefix" specs allowed. - - Args: - template: The string format template - format_dict: A {field_name: field_value_format_regex, ...} dict - process_kwargs: A function taking the field=value pairs and producing a dict of processed - {field: value,...} dict (where both fields and values could have been processed. - This is useful when we need to process (format, default, etc.) fields, or their values, - according to the other fields of values in the collection. - A specification of {field: function_to_process_this_value,...} wouldn't allow the full powers - we are allowing here. - process_info_dict: A sort of converse of format_dict. - This is a {field_name: field_conversion_func, ...} dict that is used to convert info_dict values - before returning them. - name_separator: Used - - >>> ln = StrTupleDictWithPrefix('/home/{user}/fav/{num}.txt', - ... format_dict={'user': '[^/]+', 'num': '\d+'}, - ... process_info_dict={'num': int}, - ... sep='/' - ... ) - >>> ln.mk('USER', num=123) # making a string (with args or kwargs) - '/home/USER/fav/123.txt' - >>> ####### prefix methods ####### - >>> ln.is_valid_prefix('/home/USER/fav/') - True - >>> ln.is_valid_prefix('/home/USER/fav/12') # False because too long - False - >>> ln.is_valid_prefix('/home/USER/fav') # False because too short - False - >>> ln.is_valid_prefix('/home/') # True because just right - True - >>> ln.is_valid_prefix('/home/USER/fav/123.txt') # full path, so output same as is_valid() method - True - >>> - >>> ln.mk_prefix('ME') - '/home/ME/fav/' - >>> ln.mk_prefix(user='YOU', num=456) # full specification, so output same as same as mk() method - '/home/YOU/fav/456.txt' - """ - - @wraps(StrTupleDict.__init__) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - ( - self.prefix_template_including_name, - self.prefix_template_excluding_name, - ) = mk_prefix_templates_dicts(self.template) - - _prefix_pattern = "$|".join( - [ - x.format(**self.format_dict) - for x in sorted( - list(self.prefix_template_including_name.values()), key=len - ) - ] - ) - _prefix_pattern += "$" - self.prefix_pattern = re.compile(_prefix_pattern) - - def _mk_prefix(self, *args, **kwargs): - """ - Make a prefix for an uploads name that has has the path up to the first None argument. - :return: A string that is the prefix of a valid name - """ - assert ( - len(args) + len(kwargs) <= self.n_fields - ), "You have too many arguments" - kwargs = dict({k: v for k, v in zip(self.fields, args)}, **kwargs) - if self.process_kwargs is not None: - kwargs = self.process_kwargs(**kwargs) - - # ascertain that no fields were skipped (we can leave fields out at the end, but not in the middle) - a_name_was_skipped = False - for name in self.fields: - if name not in kwargs: - if a_name_was_skipped == True: - raise ValueError( - "You are making a PREFIX: This means you can't skip any fields. " - "Once a name is omitted, you need to omit all further fields. " - f"The name order is {self.fields}. You specified {tuple(kwargs.keys())}" - ) - else: - a_name_was_skipped = True - - keep_kwargs = {} - last_name = None - for name in self.fields: - if name in kwargs: - keep_kwargs[name] = kwargs[name] - last_name = name - else: - break - - return self.prefix_template_including_name[last_name].format( - **keep_kwargs - ) - - set_signature_of_func(_mk_prefix, [(s, None) for s in self.fields]) - self.mk_prefix = MethodType(_mk_prefix, self) - -
[docs] def is_valid_prefix(self, s): - """Check if name is a valid prefix. - :param s: a string (that might or might not be a valid prefix) - :return: True iff name is a valid prefix - """ - return bool(self.prefix_pattern.match(s))
- - -LinearNaming = StrTupleDictWithPrefix - -from py2store.base import Store -from collections import namedtuple -from py2store.util import lazyprop - - -
[docs]class ParametricKeyStore(Store): - def __init__(self, store, keymap=None): - super().__init__(store) - self._keymap = keymap - - @property - def _linear_naming(self): - print("_linear_naming Deprecated: Use _keymap instead") - return self._keymap
- - -
[docs]class StoreWithTupleKeys(ParametricKeyStore): - def _id_of_key(self, key): - return self._keymap.mk(*key) - - def _key_of_id(self, _id): - return self._keymap.info_tuple(_id)
- - -
[docs]class StoreWithDictKeys(ParametricKeyStore): - def _id_of_key(self, key): - return self._keymap.mk(**key) - - def _key_of_id(self, _id): - return self._keymap.info_dict(_id)
- - -
[docs]class StoreWithNamedTupleKeys(ParametricKeyStore): - @lazyprop - def NamedTupleKey(self): - return namedtuple("NamedTupleKey", field_names=self._keymap.fields) - - def _id_of_key(self, key): - return self._keymap.mk(*key) - - def _key_of_id(self, _id): - return self.NamedTupleKey(*self._keymap.info_tuple(_id))
- - -# def mk_parametric_key_store_cls(store_cls, key_type=tuple): -# if key_type == tuple: -# super_cls = StoreWithTupleKeys -# elif key_type == dict: -# super_cls = StoreWithDictKeys -# else: -# raise ValueError("key_type needs to be tuple or dict") -# -# class A(super_cls, store_cls): -# def __init__(self, rootdir, subpath='', format_dict=None, process_kwargs=None, process_info_dict=None, -# **extra_store_kwargs): -# -# path_format = os.path.join(rootdir, subpath) -# store = store_cls.__init__(self, path_format=path_format, **extra_store_kwargs) -# linear_naming = LinearNaming() -# -# # FilepathFormatKeys.__init__(self, path_format) - - -class NamingInterface: - def __init__( - self, - params=None, - validation_funs=None, - all_kwargs_should_be_in_validation_dict=dflt_all_kwargs_should_be_in_validation_dict, - ignore_misunderstood_validation_instructions=dflt_ignore_misunderstood_validation_instructions, - **kwargs, - ): - if params is None: - params = {} - if validation_funs is None: - validation_funs = dflt_validation_funs - validation_dict = { - var: info.get("validation", {}) for var, info in params.items() - } - default_dict = { - var: info.get("default", None) for var, info in params.items() - } - arg_pattern = { - var: info.get("arg_pattern", dflt_arg_pattern) - for var, info in params.items() - } - named_arg_pattern = { - var: "(?P<" + var + ">" + pat + ")" - for var, pat in arg_pattern.items() - } - to_str = { - var: info["to_str"] - for var, info in params.items() - if "to_str" in info - } - to_val = { - var: info["to_val"] - for var, info in params.items() - if "to_val" in info - } - - self.validation_dict = validation_dict - self.default_dict = default_dict - self.arg_pattern = arg_pattern - self.named_arg_pattern = named_arg_pattern - self.to_str = to_str - self.to_val = to_val - - self.validation_funs = validation_funs - self.all_kwargs_should_be_in_validation_dict = ( - all_kwargs_should_be_in_validation_dict - ) - self.ignore_misunderstood_validation_instructions = ( - ignore_misunderstood_validation_instructions - ) - - def validate_kwargs(self, **kwargs): - return validate_kwargs( - kwargs_to_validate=kwargs, - validation_dict=self.validation_dict, - validation_funs=self.validation_funs, - all_kwargs_should_be_in_validation_dict=self.all_kwargs_should_be_in_validation_dict, - ignore_misunderstood_validation_instructions=self.ignore_misunderstood_validation_instructions, - ) - - def default_for(self, arg, **kwargs): - default = self.default_dict[arg] - if ( - not isinstance(default, dict) - or "args" not in default - or "func" not in default - ): - return default - else: # call the func on the default['args'] values given in kwargs - args = {arg_: kwargs[arg_] for arg_ in default["args"]} - return default["func"](*args) - - def str_kwargs_from(self, **kwargs): - return { - k: self.to_str[k](v) for k, v in kwargs.items() if k in self.to_str - } - - def val_kwargs_from(self, **kwargs): - return { - k: self.to_val[k](v) for k, v in kwargs.items() if k in self.to_val - } - - def name_for(self, **kwargs): - raise NotImplementedError( - "Interface method: Method needs to be implemented" - ) - - def info_for(self, **kwargs): - raise NotImplementedError( - "Interface method: Method needs to be implemented" - ) - - def is_valid_name(self, name): - raise NotImplementedError( - "Interface method: Method needs to be implemented" - ) - - -
[docs]class BigDocTest: - """ - >>> - >>> e_name = BigDocTest.mk_e_naming() - >>> u_name = BigDocTest.mk_u_naming() - >>> e_sref = 's3://bucket-GROUP/example/files/USER/SUBUSER/2017-01-24/1485272231982_1485261448469' - >>> u_sref = "s3://uploads/GROUP/upload/files/USER/2017-01-24/SUBUSER/a_file.wav" - >>> u_name_2 = "s3://uploads/ANOTHER_GROUP/upload/files/ANOTHER_USER/2017-01-24/SUBUSER/a_file.wav" - >>> - >>> ####### is_valid(self, name): ###### - >>> e_name.is_valid(e_sref) - True - >>> e_name.is_valid(u_sref) - False - >>> u_name.is_valid(u_sref) - True - >>> - >>> ####### is_valid_prefix(self, name): ###### - >>> e_name.is_valid_prefix('s3://bucket-') - True - >>> e_name.is_valid_prefix('s3://bucket-GROUP') - False - >>> e_name.is_valid_prefix('s3://bucket-GROUP/example/') - False - >>> e_name.is_valid_prefix('s3://bucket-GROUP/example/files') - False - >>> e_name.is_valid_prefix('s3://bucket-GROUP/example/files/') - True - >>> e_name.is_valid_prefix('s3://bucket-GROUP/example/files/USER/SUBUSER/2017-01-24/') - True - >>> e_name.is_valid_prefix('s3://bucket-GROUP/example/files/USER/SUBUSER/2017-01-24/0_0') - True - >>> - >>> ####### info_dict(self, name): ###### - >>> e_name.info_dict(e_sref) # see that utc_ms args were cast to ints - {'group': 'GROUP', 'user': 'USER', 'subuser': 'SUBUSER', 'day': '2017-01-24', 's_ums': 1485272231982, 'e_ums': 1485261448469} - >>> u_name.info_dict(u_sref) # returns None (because self was made for example! - {'group': 'GROUP', 'user': 'USER', 'day': '2017-01-24', 'subuser': 'SUBUSER', 'filename': 'a_file.wav'} - >>> # but with a u_name, it will work - >>> u_name.info_dict(u_sref) - {'group': 'GROUP', 'user': 'USER', 'day': '2017-01-24', 'subuser': 'SUBUSER', 'filename': 'a_file.wav'} - >>> - >>> ####### extract(self, item, name): ###### - >>> e_name.extract('group', e_sref) - 'GROUP' - >>> e_name.extract('user', e_sref) - 'USER' - >>> u_name.extract('group', u_name_2) - 'ANOTHER_GROUP' - >>> u_name.extract('user', u_name_2) - 'ANOTHER_USER' - >>> - >>> ####### mk_prefix(self, *args, **kwargs): ###### - >>> e_name.mk_prefix() - 's3://bucket-' - >>> e_name.mk_prefix(group='GROUP') - 's3://bucket-GROUP/example/files/' - >>> e_name.mk_prefix(group='GROUP', user='USER') - 's3://bucket-GROUP/example/files/USER/' - >>> e_name.mk_prefix(group='GROUP', user='USER', subuser='SUBUSER') - 's3://bucket-GROUP/example/files/USER/SUBUSER/' - >>> e_name.mk_prefix(group='GROUP', user='USER', subuser='SUBUSER', day='0000-00-00') - 's3://bucket-GROUP/example/files/USER/SUBUSER/0000-00-00/' - >>> e_name.mk_prefix(group='GROUP', user='USER', subuser='SUBUSER', day='0000-00-00', - ... s_ums=1485272231982) - 's3://bucket-GROUP/example/files/USER/SUBUSER/0000-00-00/1485272231982_' - >>> e_name.mk_prefix(group='GROUP', user='USER', subuser='SUBUSER', day='0000-00-00', - ... s_ums=1485272231982, e_ums=1485261448469) - 's3://bucket-GROUP/example/files/USER/SUBUSER/0000-00-00/1485272231982_1485261448469' - >>> - >>> u_name.mk_prefix() - 's3://uploads/' - >>> u_name.mk_prefix(group='GROUP') - 's3://uploads/GROUP/upload/files/' - >>> u_name.mk_prefix(group='GROUP', user='USER') - 's3://uploads/GROUP/upload/files/USER/' - >>> u_name.mk_prefix(group='GROUP', user='USER', day='DAY') - 's3://uploads/GROUP/upload/files/USER/DAY/' - >>> u_name.mk_prefix(group='GROUP', user='USER', day='DAY') - 's3://uploads/GROUP/upload/files/USER/DAY/' - >>> u_name.mk_prefix(group='GROUP', user='USER', day='DAY', subuser='SUBUSER') - 's3://uploads/GROUP/upload/files/USER/DAY/SUBUSER/' - >>> - >>> ####### mk(self, *args, **kwargs): ###### - >>> e_name.mk(group='GROUP', user='USER', subuser='SUBUSER', day='0000-00-00', - ... s_ums=1485272231982, e_ums=1485261448469) - 's3://bucket-GROUP/example/files/USER/SUBUSER/0000-00-00/1485272231982_1485261448469' - >>> e_name.mk(group='GROUP', user='USER', subuser='SUBUSER', day='from_s_ums', - ... s_ums=1485272231982, e_ums=1485261448469) - 's3://bucket-GROUP/example/files/USER/SUBUSER/2017-01-24/1485272231982_1485261448469' - >>> - >>> ####### replace_name_elements(self, *args, **kwargs): ###### - >>> name = 's3://bucket-redrum/example/files/oopsy@domain.com/ozeip/2008-11-04/1225779243969_1225779246969' - >>> e_name.replace_name_elements(name, user='NEW_USER', group='NEW_GROUP') - 's3://bucket-NEW_GROUP/example/files/NEW_USER/ozeip/2008-11-04/1225779243969_1225779246969' - """ - - @staticmethod - def process_info_dict_for_example(**info_dict): - if "s_ums" in info_dict: - info_dict["s_ums"] = int(info_dict["s_ums"]) - if "e_ums" in info_dict: - info_dict["e_ums"] = int(info_dict["e_ums"]) - return info_dict - - @staticmethod - def example_process_kwargs(**kwargs): - from datetime import datetime - - epoch = datetime.utcfromtimestamp(0) - second_ms = 1000.0 - - def utcnow_ms(): - return (datetime.utcnow() - epoch).total_seconds() * second_ms - - # from ut.util.time import second_ms, utcnow_ms - if "s_ums" in kwargs: - kwargs["s_ums"] = int(kwargs["s_ums"]) - if "e_ums" in kwargs: - kwargs["e_ums"] = int(kwargs["e_ums"]) - - if "day" in kwargs: - day = kwargs["day"] - # get the day in the expected format - if isinstance(day, str): - if day == "now": - day = datetime.utcfromtimestamp( - int(utcnow_ms() / second_ms) - ).strftime(day_format) - elif day == "from_s_ums": - assert "s_ums" in kwargs, "need to have s_ums argument" - day = datetime.utcfromtimestamp( - int(kwargs["s_ums"] / second_ms) - ).strftime(day_format) - else: - assert day_format_pattern.match(day) - elif isinstance(day, datetime): - day = day.strftime(day_format) - elif ( - "s_ums" in kwargs - ): # if day is neither a string nor a datetime - day = datetime.utcfromtimestamp( - int(kwargs["s_ums"] / second_ms) - ).strftime(day_format) - - kwargs["day"] = day - - return kwargs - - @staticmethod - def mk_e_naming(): - return LinearNaming( - template="s3://bucket-{group}/example/files/{user}/{subuser}/{day}/{s_ums}_{e_ums}", - format_dict={"s_ums": "\d+", "e_ums": "\d+", "day": "[^/]+"}, - process_kwargs=BigDocTest.example_process_kwargs, - process_info_dict=BigDocTest.process_info_dict_for_example, - ) - - @staticmethod - def mk_u_naming(): - return LinearNaming( - template="s3://uploads/{group}/upload/files/{user}/{day}/{subuser}/{filename}", - format_dict={"day": "[^/]+", "filepath": ".+"}, - )
- - -import os -from functools import wraps -from py2store.trans import wrap_kvs - -pjoin = os.path.join - -KeyMapNames = namedtuple("KeyMaps", ["key_of_id", "id_of_key"]) -KeyMaps = namedtuple("KeyMaps", ["key_of_id", "id_of_key"]) - - -def _get_keymap_names_for_str_to_key_type(key_type): - if not isinstance(key_type, str): - key_type = { - tuple: "tuple", - namedtuple: "namedtuple", - dict: "dict", - str: "str", - }.get(key_type, None) - - if key_type not in {"tuple", "namedtuple", "dict", "str"}: - raise ValueError(f"Not a recognized key_type: {key_type}") - - return KeyMapNames( - key_of_id=f"str_to_{key_type}", id_of_key=f"{key_type}_to_str" - ) - - -def _get_method_for_str_to_key_type(keymap, key_type): - kmn = _get_keymap_names_for_str_to_key_type(key_type) - return KeyMaps( - key_of_id=getattr(keymap, kmn.key_of_id), - id_of_key=getattr(keymap, kmn.id_of_key), - ) - - -
[docs]def mk_store_from_path_format_store_cls( - store, - subpath="", - store_cls_kwargs=None, - key_type=namedtuple, - keymap=StrTupleDict, - keymap_kwargs=None, - name=None, -): - """Wrap a store (instance or class) that uses string keys to make it into a store that uses a specific key format. - - Args: - store: The instance or class to wrap - subpath: The subpath (defining the subset of the data pointed at by the URI - store_cls_kwargs: # if store is a class, the kwargs that you would have given the store_cls to make itself - key_type: The key type you want to interface with: - dict, tuple, namedtuple, str or 'dict', 'tuple', 'namedtuple', 'str' - keymap: # the keymap instance or class you want to use to map keys - keymap_kwargs: # if keymap is a cls, the kwargs to give it (besides the subpath) - name: The name to give the class the function will make here - - Returns: An instance of a wrapped class - - - Example: - ``` - # Get a (sessiono,bt) indexed LocalJsonStore - s = mk_store_from_path_format_store_cls(LocalJsonStore, - os.path.join(root_dir, 'd'), - subpath='{session}/d/{bt}', - keymap_kwargs=dict(process_info_dict={'session': int, 'bt': int})) - ``` - """ - if isinstance(keymap, type): - keymap = keymap( - subpath, **(keymap_kwargs or {}) - ) # make the keymap instance - - km = _get_method_for_str_to_key_type(keymap, key_type) - - if isinstance(store, type): - name = name or "KeyWrapped" + store.__name__ - _WrappedStoreCls = wrap_kvs( - store, name=name, key_of_id=km.key_of_id, id_of_key=km.id_of_key - ) - - class WrappedStoreCls(_WrappedStoreCls): - def __init__(self, root_uri): - path_format = pjoin(root_uri, subpath) - super().__init__(path_format, **(store_cls_kwargs or {})) - - return WrappedStoreCls - else: - name = name or "KeyWrapped" + store.__class__.__name__ - return wrap_kvs( - store, name=name, key_of_id=km.key_of_id, id_of_key=km.id_of_key - )
- - -mk_tupled_store_from_path_format_store_cls = ( - mk_store_from_path_format_store_cls -) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/key_mappers/paths.html b/docs/_modules/py2store/key_mappers/paths.html deleted file mode 100644 index d8144d2..0000000 --- a/docs/_modules/py2store/key_mappers/paths.html +++ /dev/null @@ -1,588 +0,0 @@ - - - - - - - - py2store.key_mappers.paths — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.key_mappers.paths

-from functools import wraps, reduce
-from dataclasses import dataclass
-from typing import Union
-import os
-from warnings import warn
-
-from py2store.base import Store
-from py2store.util import lazyprop
-from py2store.trans import store_decorator
-from py2store.dig import recursive_get_attr
-
-path_sep = os.path.sep
-
-
-
[docs]class PathGetMixin: - """ - Mixin allows you to access nested stores through a path of keys. - That is, give you a path_store from a store, such that: - ``` - path_store['a','b','c'] == store['a']['b']['c'] - ``` - The mixin will check if the key is of the path type (by default `tuple`), and if it is, it will iterate through - the path, recursively getting the elements. - - Plays well with: - KeyPath - - >>> class P(PathGetMixin, dict): - ... pass - >>> s = P({'a': {'b': {'c': 42}}}) - >>> s['a'] - {'b': {'c': 42}} - >>> s['a', 'b'] - {'c': 42} - >>> s['a', 'b', 'c'] - 42 - >>> - >>> from py2store import kv_wrap - >>> class P(PathGetMixin, dict): ... - >>> PP = kv_wrap(KeyPath(path_sep='.'))(P) - >>> - >>> s = PP({'a': {'b': {'c': 42}}}) - >>> assert s['a'] == {'b': {'c': 42}} - >>> assert s['a.b'] == {'c': 42} - >>> assert s['a.b.c'] == 42 - """ - - _path_type: type = tuple - - def __getitem__(self, k): - if isinstance(k, self._path_type): - return reduce(lambda store, key: store[key], k, self) - else: - return super().__getitem__(k)
- - -
[docs]@dataclass -class KeyPath: - """ - A key mapper that converts from an iterable key (default tuple) to a string (given a path-separator str) - - Args: - path_sep: The path separator (used to make string paths from iterable paths and visa versa - _path_type: The type of the outcoming (inner) path. But really, any function to convert from a list to - the outer path type we want. - - Plays well with: - KeyPath - - >>> kp = KeyPath(path_sep='/') - >>> kp._key_of_id(('a', 'b', 'c')) - 'a/b/c' - >>> kp._id_of_key('a/b/c') - ('a', 'b', 'c') - >>> kp = KeyPath(path_sep='.') - >>> kp._key_of_id(('a', 'b', 'c')) - 'a.b.c' - >>> kp._id_of_key('a.b.c') - ('a', 'b', 'c') - >>> kp = KeyPath(path_sep=':::', _path_type=dict.fromkeys) - >>> _id = dict.fromkeys('abc') - >>> _id - {'a': None, 'b': None, 'c': None} - >>> kp._key_of_id(_id) - 'a:::b:::c' - >>> kp._id_of_key('a:::b:::c') - {'a': None, 'b': None, 'c': None} - """ - - path_sep: str = path_sep - _path_type: Union[type, callable] = tuple - - def _key_of_id(self, _id): - if not isinstance(_id, str): - return self.path_sep.join(_id) - else: - return _id - - def _id_of_key(self, k): - return self._path_type(k.split(self.path_sep))
- - -
[docs]class PrefixRelativizationMixin: - """ - Mixin that adds a intercepts the _id_of_key an _key_of_id methods, transforming absolute keys to relative ones. - Designed to work with string keys, where absolute and relative are relative to a _prefix attribute - (assumed to exist). - The cannonical use case is when keys are absolute file paths, but we want to identify data through relative paths. - Instead of referencing files through an absolute path such as - /A/VERY/LONG/ROOT/FOLDER/the/file/we.want - we can instead reference the file as - the/file/we.want - - Note though, that PrefixRelativizationMixin can be used, not only for local paths, - but when ever a string reference is involved. - In fact, not only strings, but any key object that has a __len__, __add__, and subscripting. - - When subclassed, should be placed before the class defining _id_of_key an _key_of_id. - Also, assumes that a (string) _prefix attribute will be available. - - >>> from py2store.base import Store - >>> from collections import UserDict - >>> - >>> class MyStore(PrefixRelativizationMixin, Store): - ... def __init__(self, store, _prefix='/root/of/data/'): - ... super().__init__(store) - ... self._prefix = _prefix - ... - >>> s = MyStore(store=dict()) # using a dict as our store - >>> s['foo'] = 'bar' - >>> assert s['foo'] == 'bar' - >>> s['too'] = 'much' - >>> assert list(s.keys()) == ['foo', 'too'] - >>> # Everything looks normal, but are the actual keys behind the hood? - >>> s._id_of_key('foo') - '/root/of/data/foo' - >>> # see when iterating over s.items(), we get the interface view: - >>> list(s.items()) - [('foo', 'bar'), ('too', 'much')] - >>> # but if we ask the store we're actually delegating the storing to, we see what the keys actually are. - >>> s.store.items() - dict_items([('/root/of/data/foo', 'bar'), ('/root/of/data/too', 'much')]) - """ - _prefix_attr_name = '_prefix' - - @lazyprop - def _prefix_length(self): - return len(getattr(self, self._prefix_attr_name)) - - def _id_of_key(self, k): - return getattr(self, self._prefix_attr_name) + k - - def _key_of_id(self, _id): - return _id[self._prefix_length:]
- - -
[docs]@store_decorator -def mk_relative_path_store( - store_cls=None, - *, - name=None, - with_key_validation=False, - prefix_attr="_prefix", -): - """ - - Args: - store_cls: The base store to wrap (subclass) - name: The name of the new store (by default 'RelPath' + store_cls.__name__) - with_key_validation: Whether keys should be validated upon access (store_cls must have an is_valid_key method - - Returns: A new class that uses relative paths (i.e. where _prefix is automatically added to incoming keys, - and the len(_prefix) first characters are removed from outgoing keys. - - >>> # The dynamic way (if you try this at home, be aware of the pitfalls of the dynamic way - >>> # -- but don't just believe the static dogmas). - >>> MyStore = mk_relative_path_store(dict) # wrap our favorite store: A dict. - >>> s = MyStore() # make such a store - >>> s._prefix = '/ROOT/' - >>> s['foo'] = 'bar' - >>> dict(s.items()) # gives us what you would expect - {'foo': 'bar'} - >>> # but under the hood, the dict we wrapped actually contains the '/ROOT/' prefix - >>> dict(s.store) - {'/ROOT/foo': 'bar'} - >>> - >>> # The static way: Make a class that will integrate the _prefix at construction time. - >>> class MyStore(mk_relative_path_store(dict)): # Indeed, mk_relative_path_store(dict) is a class you can subclass - ... def __init__(self, _prefix, *args, **kwargs): - ... self._prefix = _prefix - - You can choose the name you want that prefix to have as an attribute (we'll still make - a hidden '_prefix' attribute for internal use, but at least you can have an attribute with the - name you want. - - >>> MyRelStore = mk_relative_path_store(dict, prefix_attr='rootdir') - >>> s = MyRelStore() - >>> s.rootdir = '/ROOT/' - - >>> s['foo'] = 'bar' - >>> dict(s.items()) # gives us what you would expect - {'foo': 'bar'} - >>> # but under the hood, the dict we wrapped actually contains the '/ROOT/' prefix - >>> dict(s.store) - {'/ROOT/foo': 'bar'} - - """ - # name = name or ("RelPath" + store_cls.__name__) - # __module__ = __module__ or getattr(store_cls, "__module__", None) - - if name is not None: - from warnings import warn - warn(f"The use of name argumment is deprecated. Use __name__ instead", DeprecationWarning) - - cls = type(store_cls.__name__, (PrefixRelativizationMixin, Store), {}) - - @wraps(store_cls.__init__) - def __init__(self, *args, **kwargs): - Store.__init__(self, store=store_cls(*args, **kwargs)) - prefix = recursive_get_attr(self.store, prefix_attr, "") - setattr( - self, prefix_attr, prefix - ) # TODO: Might need descriptor to enable assignment - - cls.__init__ = __init__ - - if prefix_attr != '_prefix': - assert not hasattr(store_cls, '_prefix'), f"You already have a _prefix attribute, " \ - f"but want the prefix name to be {prefix_attr}. " \ - f"That's not going to be easy for me." - - # if not hasattr(cls, prefix_attr): - # warn(f"You said you wanted prefix_attr='{prefix_attr}', " - # f"but {cls} (the wrapped class) doesn't have a '{prefix_attr}'. " - # f"I'll let it slide because perhaps the attribute is dynamic. But I'm warning you!!") - - @property - def _prefix(self): - return getattr(self, prefix_attr) - - cls._prefix = _prefix - - if with_key_validation: - assert hasattr(store_cls, 'is_valid_key'), "If you want with_key_validation=True, " \ - "you'll need a method called is_valid_key to do the validation job" - - def _id_of_key(self, k): - _id = super(cls, self)._id_of_key(k) - if self.store.is_valid_key(_id): - return _id - else: - raise KeyError( - f"Key not valid (usually because does not exist or access not permitted): {k}" - ) - - cls._id_of_key = _id_of_key - - # if __module__ is not None: - # cls.__module__ = __module__ - - # print(callable(cls)) - - return cls
- - -# TODO: Intended to replace the init-less PrefixRelativizationMixin -# (but should change name if so, since Mixins shouldn't have inits) -class RelativePathKeyMapper: - def __init__(self, prefix): - self._prefix = prefix - self._prefix_length = len(self._prefix) - - def _id_of_key(self, k): - return self._prefix + k - - def _key_of_id(self, _id): - return _id[self._prefix_length:] - - -from py2store.key_mappers.naming import StrTupleDict -from enum import Enum - - -
[docs]class PathKeyTypes(Enum): - str = 'str' - dict = 'dict' - tuple = 'tuple' - namedtuple = 'namedtuple'
- - -_method_names_for_path_type = { - PathKeyTypes.str: {'_id_of_key': StrTupleDict.simple_str_to_str, - '_key_of_id': StrTupleDict.str_to_simple_str}, - PathKeyTypes.dict: {'_id_of_key': StrTupleDict.dict_to_str, - '_key_of_id': StrTupleDict.str_to_dict}, - PathKeyTypes.tuple: {'_id_of_key': StrTupleDict.tuple_to_str, - '_key_of_id': StrTupleDict.str_to_tuple}, - PathKeyTypes.namedtuple: {'_id_of_key': StrTupleDict.namedtuple_to_str, - '_key_of_id': StrTupleDict.str_to_namedtuple}, -} - - -# -# def str_to_simple_str(self, s: str): -# return self.sep.join(*self.str_to_tuple(s)) -# -# -# def simple_str_to_str(self, ss: str): -# self.tuple_to_str(self.si) - -# TODO: Add key and id type validation -
[docs]def str_template_key_trans( - template: str, - key_type: PathKeyTypes, - format_dict=None, - process_kwargs=None, - process_info_dict=None, - named_tuple_type_name="NamedTuple", - sep: str = path_sep, -): - """Make a key trans object that translates from a string _id to a dict, tuple, or namedtuple key (and back)""" - - assert key_type in PathKeyTypes, f"key_type was {key_type}. Needs to be one of these: {', '.join(PathKeyTypes)}" - - class PathKeyMapper(StrTupleDict): - ... - - setattr(PathKeyMapper, '_id_of_key', _method_names_for_path_type[key_type]['_id_of_key']) - setattr(PathKeyMapper, '_key_of_id', _method_names_for_path_type[key_type]['_key_of_id']) - - key_trans = PathKeyMapper(template, format_dict, process_kwargs, - process_info_dict, named_tuple_type_name, sep) - - return key_trans
- - -str_template_key_trans.method_names_for_path_type = _method_names_for_path_type -str_template_key_trans.key_types = PathKeyTypes - - -# TODO: Merge with mk_relative_path_store -
[docs]def rel_path_wrap(o, _prefix): - """ - Args: - o: An object to be wrapped - _prefix: The _prefix to use for key wrapping (will remove it from outcoming keys and add to ingoing keys. - - >>> # The dynamic way (if you try this at home, be aware of the pitfalls of the dynamic way - >>> # -- but don't just believe the static dogmas). - >>> d = {'/ROOT/of/every/thing': 42, '/ROOT/of/this/too': 0} - >>> dd = rel_path_wrap(d, '/ROOT/of/') - >>> dd['foo'] = 'bar' - >>> dict(dd.items()) # gives us what you would expect - {'every/thing': 42, 'this/too': 0, 'foo': 'bar'} - >>> # but under the hood, the dict we wrapped actually contains the '/ROOT/' prefix - >>> dict(dd.store) - {'/ROOT/of/every/thing': 42, '/ROOT/of/this/too': 0, '/ROOT/of/foo': 'bar'} - >>> - >>> # The static way: Make a class that will integrate the _prefix at construction time. - >>> class MyStore(mk_relative_path_store(dict)): # Indeed, mk_relative_path_store(dict) is a class you can subclass - ... def __init__(self, _prefix, *args, **kwargs): - ... self._prefix = _prefix - - """ - - from py2store import kv_wrap - - trans_obj = RelativePathKeyMapper(_prefix) - return kv_wrap(trans_obj)(o)
- -# mk_relative_path_store_cls = mk_relative_path_store # alias - -## Alternative to mk_relative_path_store that doesn't make lint complain (but the repr shows MyStore, not name) -# def mk_relative_path_store_alt(store_cls, name=None): -# if name is None: -# name = 'RelPath' + store_cls.__name__ -# -# class MyStore(PrefixRelativizationMixin, Store): -# @wraps(store_cls.__init__) -# def __init__(self, *args, **kwargs): -# super().__init__(store=store_cls(*args, **kwargs)) -# self._prefix = self.store._prefix -# MyStore.__name__ = name -# -# return MyStore -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/key_mappers/str_utils.html b/docs/_modules/py2store/key_mappers/str_utils.html deleted file mode 100644 index 693a069..0000000 --- a/docs/_modules/py2store/key_mappers/str_utils.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - - - - - py2store.key_mappers.str_utils — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.key_mappers.str_utils

-"""
-utils from strings
-"""
-import string
-
-dflt_formatter = string.Formatter()
-
-
-# For testing:
-# position =  "all/{}/is/{}/position"
-# position_explicit =  "all/{1}/is/{0}/position"
-# position_hybrid =  "all/{}/is/{0}/position/{except}{this}"
-# keyword = "all/{this}/is/{with}/keywords"
-# hybrid = "and/{this}/is/{}/hybrid"
-
-
-def parse_str_format(str_format):
-    return list(dflt_formatter.parse(str_format))
-
-
-
[docs]def get_explicit_positions(parsed_str_format): - """ - >>> parsed = parse_str_format("all/{}/is/{2}/position/{except}{this}{0}") - >>> get_explicit_positions(parsed) - {0, 2} - """ - return set( - map( - int, - filter( - lambda x: isinstance(x, str) and str.isnumeric(x), - (x[1] for x in parsed_str_format), - ), - ) - )
- - -
[docs]def compile_str_from_parsed(parsed): - """The (quasi-)inverse of string.Formatter.parse. - - Args: - parsed: iterator of (literal_text, field_name, format_spec, conversion) tuples, - as yield by string.Formatter.parse - - Returns: - A format string that would produce such a parsed input. - - >>> s = "ROOT/{}/{0!r}/{1!i:format}/hello{:0.02f}TAIL" - >>> assert compile_str_from_parsed(string.Formatter().parse(s)) == s - >>> - >>> # Or, if you want to see more details... - >>> parsed = list(string.Formatter().parse(s)) - >>> for p in parsed: - ... print(p) - ('ROOT/', '', '', None) - ('/', '0', '', 'r') - ('/', '1', 'format', 'i') - ('/hello', '', '0.02f', None) - ('TAIL', None, None, None) - >>> compile_str_from_parsed(parsed) - 'ROOT/{}/{0!r}/{1!i:format}/hello{:0.02f}TAIL' - """ - result = '' - for literal_text, field_name, format_spec, conversion in parsed: - # output the literal text - if literal_text: - result += literal_text - - # if there's a field, output it - if field_name is not None: - result += '{' - if field_name != '': - result += field_name - if conversion: - result += '!' + conversion - if format_spec: - result += ':' + format_spec - result += '}' - return result
- - -def transform_format_str(format_str, parsed_tuple_trans_func): - return compile_str_from_parsed( - map( - lambda args: parsed_tuple_trans_func(*args), - dflt_formatter.parse(format_str), - ) - ) - - -def _empty_field_name(literal_text, field_name, format_spec, conversion): - if field_name is not None: - return literal_text, '', format_spec, conversion - else: - return literal_text, field_name, format_spec, conversion - - -
[docs]def auto_field_format_str(format_str): - """Get an auto field version of the format_str - - Args: - format_str: A format string - - Returns: - A transformed format_str that has no names {inside} {formatting} {braces}. - >>> auto_field_format_str('R/{0}/{one}/{}/{two}/T') - 'R/{}/{}/{}/{}/T' - """ - return transform_format_str(format_str, _empty_field_name)
- - -
[docs]def manual_field_format_str(format_str): - """Get an auto field version of the format_str - - Args: - format_str: A format string - - Returns: - A transformed format_str that has no names {inside} {formatting} {braces}. - >>> auto_field_format_str('R/{0}/{one}/{}/{two}/T') - 'R/{}/{}/{}/{}/T' - """ - return transform_format_str(format_str, _empty_field_name)
- - -def _mk_naming_trans_func(names=None): - if names is None: - names = map(str, range(99999)) - _names = iter(names) - - def trans_func(literal_text, field_name, format_spec, conversion): - if field_name is not None: - return literal_text, next(_names), format_spec, conversion - else: - return literal_text, field_name, format_spec, conversion - - return trans_func - - -
[docs]def name_fields_in_format_str(format_str, field_names=None): - """Get a manual field version of the format_str - - Args: - format_str: A format string - names: An iterable that produces enough strings to fill all of format_str fields - - Returns: - A transformed format_str - >>> name_fields_in_format_str('R/{0}/{one}/{}/{two}/T') - 'R/{0}/{1}/{2}/{3}/T' - >>> # Note here that we use the field name to inject a field format as well - >>> name_fields_in_format_str('R/{foo}/{0}/{}/T', ['42', 'hi:03.0f', 'world']) - 'R/{42}/{hi:03.0f}/{world}/T' - """ - return transform_format_str(format_str, _mk_naming_trans_func(field_names))
- - -no_hybrid_format_error = ValueError( - 'cannot switch from manual field specification (i.e. {{number}} or {{name}}) ' - 'to automatic (i.e. {{}}) field numbering.' -) - - -def _is_not_none(x): - return x is not None - - -
[docs]def format_params_in_str_format(format_string): - """ - Get the "parameter" indices/names of the format_string - - Args: - format_string: A format string (i.e. a string with {...} to mark parameter placement and formatting - - Returns: - A list of parameter indices used in the format string, in the order they appear, with repetition. - Parameter indices could be integers, strings, or None (to denote "automatic field numbering". - >>> format_string = '{0} (no 1) {2}, and {0} is a duplicate, {} is unnamed and {name} is string-named' - >>> format_params_in_str_format(format_string) - [0, 2, 0, None, 'name'] - """ - return list( - map( - lambda x: int(x) if str.isnumeric(x) else x if x != '' else None, - filter( - _is_not_none, - (x[1] for x in dflt_formatter.parse(format_string)), - ), - ) - )
- - -
[docs]def n_format_params_in_str_format(format_string): - """ The number of parameters""" - return len(set(format_params_in_str_format(format_string)))
- - -
[docs]def is_manual_format_string(format_string): - """ Says if the format_string uses a manual specification - See Also: is_automatic_format_string and - >>> is_manual_format_string('Manual: indices: {1} {2}, named: {named} {fields}') - True - >>> is_manual_format_string('Auto: only un-indexed and un-named: {} {}...') - False - >>> is_manual_format_string('Hybrid: at least a {}, and a {0} or a {name}') - False - >>> is_manual_format_string('No formatting is both manual and automatic formatting!') - True - """ - return is_manual_format_params(format_params_in_str_format(format_string))
- - -
[docs]def is_automatic_format_string(format_string): - """ Says if the format_string is uses automatic specification - See Also: is_manual_format_params - >>> is_automatic_format_string('Manual: indices: {1} {2}, named: {named} {fields}') - False - >>> is_automatic_format_string('Auto: only un-indexed and un-named: {} {}...') - True - >>> is_automatic_format_string('Hybrid: at least a {}, and a {0} or a {name}') - False - >>> is_manual_format_string('No formatting is both manual and automatic formatting!') - True - """ - return is_automatic_format_params( - format_params_in_str_format(format_string) - )
- - -
[docs]def is_hybrid_format_string(format_string): - """ Says if the format_params is from a hybrid of auto and manual. - Note: Hybrid specifications are considered non-valid and can't be formatted with format_string.format(...). - Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). - - >>> is_hybrid_format_string('Manual: indices: {1} {2}, named: {named} {fields}') - False - >>> is_hybrid_format_string('Auto: only un-indexed and un-named: {} {}...') - False - >>> is_hybrid_format_string('Hybrid: at least a {}, and a {0} or a {name}') - True - >>> is_manual_format_string('No formatting is both manual and automatic formatting (so hybrid is both)!') - True - """ - return is_hybrid_format_params(format_params_in_str_format(format_string))
- - -
[docs]def is_manual_format_params(format_params): - """ Says if the format_params is from a manual specification - See Also: is_automatic_format_params - """ - assert not isinstance( - format_params, str - ), "format_params can't be a string (perhaps you meant is_manual_format_string?)" - return all((x is not None) for x in format_params)
- - -
[docs]def is_automatic_format_params(format_params): - """ Says if the format_params is from an automatic specification - See Also: is_manual_format_params and is_hybrid_format_params - """ - assert not isinstance( - format_params, str - ), "format_params can't be a string (perhaps you meant to use is_automatic_format_string?)" - return all((x is None) for x in format_params)
- - -
[docs]def is_hybrid_format_params(format_params): - """ Says if the format_params is from a hybrid of auto and manual. - Note: Hybrid specifications are considered non-valid and can't be formatted with format_string.format(...). - Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). - See Also: is_manual_format_params and is_automatic_format_params - """ - assert not isinstance( - format_params, str - ), "format_params can't be a string (perhaps you meant is_hybrid_format_string?)" - return (not is_manual_format_params(format_params)) and ( - not is_automatic_format_params(format_params) - )
- - -def empty_arg_and_kwargs_for_format(format_string, fill_val=None): - format_params = format_params_in_str_format(format_string) - if is_manual_format_params(format_params): - args_keys, kwargs_keys = args_and_kwargs_indices(format_string) - args = [fill_val] * ( - max(args_keys) + 1 - ) # max because e.g., sometimes, we have {0} and {2} without a {1} - kwargs = {k: fill_val for k in kwargs_keys} - elif is_automatic_format_params(format_params): - args = [fill_val] * len(format_params) - kwargs = {} - else: - raise no_hybrid_format_error - # filled_format_string = mk_manual_spec_format_string(format_string, names=()) - - return args, kwargs - - -# def mk_manual_spec_format_string(format_string, names=()): -# pass - - -
[docs]def args_and_kwargs_indices(format_string): - """ Get the sets of indices and names used in manual specification of format strings, or None, None if auto spec. - Args: - format_string: A format string (i.e. a string with {...} to mark parameter placement and formatting - - Returns: - None, None if format_string is an automatic specification - set_of_indices_used, set_of_fields_used if it is a manual specification - >>> format_string = '{0} (no 1) {2}, {see} this, {0} is a duplicate (appeared before) and {name} is string-named' - >>> assert args_and_kwargs_indices(format_string) == ({0, 2}, {'name', 'see'}) - >>> format_string = 'This is a format string with only automatic field specification: {}, {}, {} etc.' - >>> assert args_and_kwargs_indices(format_string) == (set(), set()) - """ - if is_hybrid_format_string(format_string): - raise no_hybrid_format_error - d = {True: set(), False: set()} - for x in format_params_in_str_format(format_string): - if x is not None: - d[isinstance(x, int)].add(x) - args_keys, kwargs_keys = d[True], d[False] - return args_keys, kwargs_keys
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/key_mappers/tuples.html b/docs/_modules/py2store/key_mappers/tuples.html deleted file mode 100644 index b42ddd2..0000000 --- a/docs/_modules/py2store/key_mappers/tuples.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - py2store.key_mappers.tuples — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.key_mappers.tuples

-"""
-Tools to map tuple-structured keys.
-That is, converting from any of the following kinds of keys:
-    * tuples (or list-like)
-    * dicts
-    * formatted/templated strings
-    * dsv (Delimiter-Separated Values)
-
-"""
-
-# TODO: Add short docs
-# TODO: Add SIMPLE (just one or two tests) doctests.
-# TODO: Add randomized "bijectivity" tests (see _test_dsv_of_list for what I mean) if easy.
-
-from functools import partial
-from py2store.errors import KeyValidationError, _assert_condition
-
-__assert_condition = partial(_assert_condition, err_cls=KeyValidationError)
-
-
-def tuple_of_dict(d, fields):
-    __assert_condition(
-        len(fields) == len(d), f'len(d)={len(d)} but len(fields)={len(fields)}'
-    )
-    return tuple(d[f] for f in fields)
-
-
-def dict_of_tuple(d, fields):
-    __assert_condition(
-        len(fields) == len(d), f'len(d)={len(d)} but len(fields)={len(fields)}'
-    )
-    return {f: x for f, x in zip(fields, d)}
-
-
-
[docs]def str_of_tuple(d, str_format): - """Convert tuple to str. - It's just str_format.format(*d). Why even write such a function? - (1) To have a consistent interface for key conversions - (2) We want a KeyValidationError to occur here - Args: - d: tuple if params to str_format - str_format: Auto fields format string. If you have manual fields, consider auto_field_format_str to convert. - - Returns: - parametrized string - - >>> str_of_tuple(('hello', 'world'), "Well, {} dear {}!") - 'Well, hello dear world!' - """ - try: - return str_format.format(*d) - except Exception as e: - raise KeyValidationError(e)
- - -def tuple_of_str(d, compiled_regex): - m = compiled_regex.match(d) - if m: - return m.groups() - else: - raise KeyValidationError( - f"The string {d} didn't match the pattern {compiled_regex}" - ) - - -def str_of_dict(d, str_format): - try: - return str_format.format(**d) - except Exception as e: - raise KeyValidationError(e) - - -def dict_of_str(d, compiled_regex): - m = compiled_regex.match(d) - if m: - return m.groupdict() - else: - raise KeyValidationError( - f"The string {d} didn't match the pattern {compiled_regex}" - ) - - -
[docs]def mk_str_of_obj(attrs): - """Make a function that transforms objects to strings, using specific attributes of object. - - Args: - attrs: Attributes that should be read off of the object to make the parameters of the string - - Returns: - A transformation function - - >>> from dataclasses import dataclass - >>> @dataclass - ... class A: - ... foo: int - ... bar: str - >>> a = A(foo=0, bar='rin') - >>> a - A(foo=0, bar='rin') - >>> - >>> str_from_obj = mk_str_of_obj(['foo', 'bar']) - >>> str_from_obj(a, 'ST{foo}/{bar}/G') - 'ST0/rin/G' - """ - - def dict_of_obj(o): - return {k: getattr(o, k) for k in attrs} - - def str_of_obj(d, str_format): - return str_of_dict(dict_of_obj(d), str_format) - - return str_of_obj
- - -
[docs]def mk_obj_of_str(constructor): - """Make a function that transforms a string to an object. The factory making inverses of what mk_str_from_obj makes. - - Args: - constructor: The function (or class) that will be used to make objects from the **kwargs parsed out of the - string. - - Returns: - A function factory. - - """ - - def obj_of_str(d, compiled_regex): - constructor(**dict_of_str(d, compiled_regex)) - - return obj_of_str
- - -
[docs]def dsv_of_list(d, sep=','): - """ - Converting a list of strings to a dsv (delimiter-separated values) string. - - Note that unlike most key mappers, there is no schema imposing size here. If you wish to impose a size - validation, do so externally (we suggest using a decorator for that). - - Args: - d: A list of component strings - sep: The delimiter text used to separate a string into a list of component strings - - Returns: - The delimiter-separated values (dsv) string for the input tuple - - >>> dsv_of_list(['a', 'brown', 'fox'], sep=' ') - 'a brown fox' - >>> dsv_of_list(('jumps', 'over'), sep='/') # for filepaths (and see that tuple inputs work too!) - 'jumps/over' - >>> dsv_of_list(['Sat', 'Jan', '1', '1983'], sep=',') # csv: the usual delimiter-separated values format - 'Sat,Jan,1,1983' - >>> dsv_of_list(['First', 'Last'], sep=':::') # a longer delimiter - 'First:::Last' - >>> dsv_of_list(['singleton'], sep='@') # when the list has only one element - 'singleton' - >>> dsv_of_list([], sep='@') # when the list is empty - '' - """ - return sep.join(d)
- - -
[docs]def list_of_dsv(d, sep=','): - """ - Converting a dsv (delimiter-separated values) string to the list of it's components. - - Args: - d: A (delimiter-separated values) string - sep: The delimiter text used to separate the string into a list of component strings - - Returns: - A list of component strings corresponding to the input delimiter-separated values (dsv) string - - >>> list_of_dsv('a brown fox', sep=' ') - ['a', 'brown', 'fox'] - >>> tuple(list_of_dsv('jumps/over', sep='/')) # for filepaths - ('jumps', 'over') - >>> list_of_dsv('Sat,Jan,1,1983', sep=',') # csv: the usual delimiter-separated values format - ['Sat', 'Jan', '1', '1983'] - >>> list_of_dsv('First:::Last', sep=':::') # a longer delimiter - ['First', 'Last'] - >>> list_of_dsv('singleton', sep='@') # when the list has only one element - ['singleton'] - >>> list_of_dsv('', sep='@') # when the string is empty - [] - """ - if ( - not d - ): # doing this, because split returns [''] on an empty string (bad choice if you ask me!) - return [] - else: - return d.split(sep)
- - -def _test_dsv_of_list(n_tests=100, max_n_elements=10, max_sep_length=3): - import random - import string - - alphanumeric = string.digits + string.ascii_lowercase - non_alphanumeric = ''.join(set(string.printable).difference(alphanumeric)) - - def random_string(length=7, character_set=alphanumeric): - return ''.join(random.choice(character_set) for _ in range(length)) - - for i in range(n_tests): - for n_elements in random.choice(range(1, max_n_elements + 1)): - words = [x for x in random_string(n_elements, alphanumeric)] - sep_length = random.choice(range(1, max_sep_length + 1)) - sep = random_string(sep_length, non_alphanumeric) - dsv_line = dsv_of_list(words, sep) - dsv_words = list_of_dsv(dsv_line, sep) - assert all( - dsv_words == words - ), f'Expected:\n\t{words}\nGot:\n\t{dsv_words}' - - -if __name__ == '__main__': - _test_dsv_of_list() -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/misc.html b/docs/_modules/py2store/misc.html deleted file mode 100644 index 0e87b2b..0000000 --- a/docs/_modules/py2store/misc.html +++ /dev/null @@ -1,599 +0,0 @@ - - - - - - - - py2store.misc — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.misc

-"""Functions to read from and write to misc sources
-"""
-
-import os
-import json
-import pickle
-import csv
-import gzip
-from io import StringIO
-
-from py2store.stores.local_store import LocalBinaryStore
-from py2store.slib.s_zipfile import FilesOfZip
-from py2store.slib.s_configparser import ConfigReader, ConfigStore
-from py2store.util import imdict
-
-
-def csv_fileobj(
-    csv_data, *args, **kwargs
-):  # TODO: Use extended wraps func to inject
-    fp = StringIO('')
-    writer = csv.writer(fp)
-    writer.writerows(csv_data, *args, **kwargs)
-    fp.seek(0)
-    return fp.read().encode()
-
-
-def identity_method(x):
-    return x
-
-
-# TODO: Enhance default handling so users can have their own defaults (checking for local config file etc.)
-# Note: If you're tempted to add third-party cases here (like yaml, pandas):
-#   DO NOT!! Defaults must work only with builtins (or misc would be non-deterministic)
-dflt_func_key = lambda self, k: os.path.splitext(k)[1]
-dflt_dflt_incoming_val_trans = staticmethod(identity_method)
-
-dflt_incoming_val_trans_for_key = {
-    '.bin': identity_method,
-    '.csv': lambda v: list(csv.reader(StringIO(v.decode()))),
-    '.txt': lambda v: v.decode(),
-    '.pkl': lambda v: pickle.loads(v),
-    '.pickle': lambda v: pickle.loads(v),
-    '.json': lambda v: json.loads(v),
-    '.zip': FilesOfZip,
-    '.gzip': gzip.decompress,
-    '.ini': lambda v: ConfigStore(
-        v, interpolation=ConfigReader.ExtendedInterpolation(),
-    ),
-}
-
-dflt_outgoing_val_trans_for_key = {
-    '.bin': identity_method,
-    '.csv': csv_fileobj,
-    '.txt': lambda v: v.encode(),
-    '.pkl': lambda v: pickle.dumps(v),
-    '.pickle': lambda v: pickle.dumps(v),
-    '.json': lambda v: json.dumps(v).encode(),
-    '.gzip': gzip.compress,
-    '.ini': lambda v: ConfigStore(
-        v, interpolation=ConfigReader.ExtendedInterpolation()
-    ),
-}
-
-synset_of_ext = {'.ini': {'.cnf', '.conf', '.config'}, '.gzip': ['.gz']}
-for _user_this, _for_these_extensions in synset_of_ext.items():
-    for _d in [
-        dflt_incoming_val_trans_for_key,
-        dflt_outgoing_val_trans_for_key,
-    ]:
-        if _user_this in _d:
-            for _ext in _for_these_extensions:
-                _d[_ext] = _d[_user_this]
-
-
-# TODO: Different misc objects (function, class, default instance) should be a aligned more
-
-
-
[docs]class MiscReaderMixin: - """Mixin to transform incoming vals according to the key their under. - Warning: If used as a subclass, this mixin should (in general) be placed before the store - - - >>> # make a reader that will wrap a dict - >>> class MiscReader(MiscReaderMixin, dict): - ... def __init__(self, d, - ... incoming_val_trans_for_key=None, - ... dflt_incoming_val_trans=None, - ... func_key=None): - ... dict.__init__(self, d) - ... MiscReaderMixin.__init__(self, incoming_val_trans_for_key, dflt_incoming_val_trans, func_key) - ... - >>> - >>> incoming_val_trans_for_key = dict( - ... MiscReaderMixin._incoming_val_trans_for_key, # take the existing defaults... - ... **{'.bin': lambda v: [ord(x) for x in v.decode()], # ... override how to handle the .bin extension - ... '.reverse_this': lambda v: v[::-1] # add a new extension (and how to handle it) - ... }) - >>> - >>> import pickle - >>> d = { - ... 'a.bin': b'abc123', - ... 'a.reverse_this': b'abc123', - ... 'a.csv': b'event,year\\n Magna Carta,1215\\n Guido,1956', - ... 'a.txt': b'this is not a text', - ... 'a.pkl': pickle.dumps(['text', [str, map], {'a list': [1, 2, 3]}]), - ... 'a.json': '{"str": "field", "int": 42, "float": 3.14, "array": [1, 2], "nested": {"a": 1, "b": 2}}', - ... } - >>> - >>> s = MiscReader(d=d, incoming_val_trans_for_key=incoming_val_trans_for_key) - >>> list(s) - ['a.bin', 'a.reverse_this', 'a.csv', 'a.txt', 'a.pkl', 'a.json'] - >>> s['a.bin'] - [97, 98, 99, 49, 50, 51] - >>> s['a.reverse_this'] - b'321cba' - >>> s['a.csv'] - [['event', 'year'], [' Magna Carta', '1215'], [' Guido', '1956']] - >>> s['a.pkl'] - ['text', [<class 'str'>, <class 'map'>], {'a list': [1, 2, 3]}] - >>> s['a.json'] - {'str': 'field', 'int': 42, 'float': 3.14, 'array': [1, 2], 'nested': {'a': 1, 'b': 2}} - """ - - _func_key = lambda self, k: os.path.splitext(k)[1] - _dflt_incoming_val_trans = staticmethod(identity_method) - - _incoming_val_trans_for_key = imdict(dflt_incoming_val_trans_for_key) - - def __init__( - self, - incoming_val_trans_for_key=None, - dflt_incoming_val_trans=None, - func_key=None, - ): - if incoming_val_trans_for_key is not None: - self._incoming_val_trans_for_key = incoming_val_trans_for_key - if dflt_incoming_val_trans is not None: - self._dflt_incoming_val_trans = dflt_incoming_val_trans - if func_key is not None: - self._func_key = func_key - - def __getitem__(self, k): - func_key = self._func_key(k) - trans_func = self._incoming_val_trans_for_key.get( - func_key, self._dflt_incoming_val_trans - ) - return trans_func(super().__getitem__(k))
- - -try: - from py2store.examples.dropbox_w_urllib import bytes_from_dropbox -except Exception: - _dropbox_as_special_case = False -else: - _dropbox_as_special_case = True - - -# TODO: I'd really like to reuse MiscReaderMixin here! There's a lot of potential. -# TODO: For more flexibility, the default store should probably be a UriReader (that doesn't exist yet) -# If store argument of get_obj was a type instead of an instance, or if MiscReaderMixin was a transformer, if would -# be easier -- but would it make their individual concerns mixed? -# Also, preset and postget (trans.wrap_kvs(...)) now exist. Let's use them here. -
[docs]def get_obj( - k, - store=LocalBinaryStore(path_format=''), - incoming_val_trans_for_key=imdict(dflt_incoming_val_trans_for_key), - dflt_incoming_val_trans=identity_method, - func_key=lambda k: os.path.splitext(k)[1], -): - """A quick way to get an object, with default... everything (but the key, you know, a clue of what you want)""" - if k.startswith('http://') or k.startswith('https://'): - if _dropbox_as_special_case and ( - k.startswith('http://www.dropbox.com') - or k.startswith('https://www.dropbox.com') - ): - v = bytes_from_dropbox(k) - else: - import urllib.request - - with urllib.request.urlopen(k) as response: - v = response.read() - else: - if isinstance( - store, LocalBinaryStore - ): # being extra careful to only do this if default local store - # preprocessing the key if it starts with '.', '..', or '~' - if k.startswith('.') or k.startswith('..'): - k = os.path.abspath(k) - elif k.startswith('~'): - k = os.path.expanduser(k) - v = store[k] - trans_func = (incoming_val_trans_for_key or {}).get( - func_key(k), dflt_incoming_val_trans - ) - return trans_func(v)
- - -# TODO: I'd really like to reuse MiscReaderMixin here! There's a lot of potential. -# Same comment as for get_obj. -
[docs]class MiscGetter: - """ - An object to write (and only write) to a store (default local files) with automatic deserialization - according to a property of the key (default: file extension). - - >>> from py2store.misc import get_obj, misc_objs_get - >>> import os - >>> import json - >>> - >>> pjoin = lambda *p: os.path.join(os.path.expanduser('~'), *p) - >>> path = pjoin('tmp.json') - >>> d = {'a': {'b': {'c': [1, 2, 3]}}} - >>> json.dump(d, open(path, 'w')) # putting a json file there, the normal way, so we can use it later - >>> - >>> k = path - >>> t = get_obj(k) # if you'd like to use a function - >>> assert t == d - >>> tt = misc_objs_get[k] # if you'd like to use an object (note: can get, but nothing else (no list, set, del, etc)) - >>> assert tt == d - >>> t - {'a': {'b': {'c': [1, 2, 3]}}} - """ - - def __init__( - self, - store=LocalBinaryStore(path_format=''), - incoming_val_trans_for_key=imdict(dflt_incoming_val_trans_for_key), - dflt_incoming_val_trans=identity_method, - func_key=lambda k: os.path.splitext(k)[1], - ): - self.store = store - self.incoming_val_trans_for_key = incoming_val_trans_for_key - self.dflt_incoming_val_trans = dflt_incoming_val_trans - self.func_key = func_key - - def __getitem__(self, k): - return get_obj( - k, - self.store, - self.incoming_val_trans_for_key, - self.dflt_incoming_val_trans, - self.func_key, - ) - - def __iter__(self): - # Disabling "manually" to avoid iteration falling back on __getitem__ with integers - # To know more, see: - # https://stackoverflow.com/questions/37941523/pip-uninstall-no-files-were-found-to-uninstall - # https://www.python.org/dev/peps/pep-0234/ - - raise NotImplementedError( - "By default, there's no iteration in MiscGetter. " - 'But feel free to subclass if you ' - 'have a particular sense of what the iteration should yield!' - )
- - -misc_objs_get = MiscGetter() - -# TODO: Make this be more tightly couples with the actual default used in get_obj and MiscGetter (avoid misalignments) -misc_objs_get.dflt_incoming_val_trans_for_key = dflt_incoming_val_trans_for_key - - -
[docs]class MiscStoreMixin(MiscReaderMixin): - r"""Mixin to transform incoming and outgoing vals according to the key their under. - Warning: If used as a subclass, this mixin should (in general) be placed before the store - - See also: preset and postget args from wrap_kvs decorator from py2store.trans. - - >>> # Make a class to wrap a dict with a layer that transforms written and read values - >>> class MiscStore(MiscStoreMixin, dict): - ... def __init__(self, d, - ... incoming_val_trans_for_key=None, outgoing_val_trans_for_key=None, - ... dflt_incoming_val_trans=None, dflt_outgoing_val_trans=None, - ... func_key=None): - ... dict.__init__(self, d) - ... MiscStoreMixin.__init__(self, incoming_val_trans_for_key, outgoing_val_trans_for_key, - ... dflt_incoming_val_trans, dflt_outgoing_val_trans, func_key) - ... - >>> - >>> outgoing_val_trans_for_key = dict( - ... MiscStoreMixin._outgoing_val_trans_for_key, # take the existing defaults... - ... **{'.bin': lambda v: ''.join([chr(x) for x in v]).encode(), # ... override how to handle the .bin extension - ... '.reverse_this': lambda v: v[::-1] # add a new extension (and how to handle it) - ... }) - >>> ss = MiscStore(d={}, # store starts empty - ... incoming_val_trans_for_key={}, # overriding incoming trans so we can see the raw data later - ... outgoing_val_trans_for_key=outgoing_val_trans_for_key) - ... - >>> # here's what we're going to write in the store - >>> data_to_write = { - ... 'a.bin': [97, 98, 99, 49, 50, 51], - ... 'a.reverse_this': b'321cba', - ... 'a.csv': [['event', 'year'], [' Magna Carta', '1215'], [' Guido', '1956']], - ... 'a.txt': 'this is not a text', - ... 'a.pkl': ['text', [str, map], {'a list': [1, 2, 3]}], - ... 'a.json': {'str': 'field', 'int': 42, 'float': 3.14, 'array': [1, 2], 'nested': {'a': 1, 'b': 2}}} - >>> # write this data in our store - >>> for k, v in data_to_write.items(): - ... ss[k] = v - >>> list(ss) - ['a.bin', 'a.reverse_this', 'a.csv', 'a.txt', 'a.pkl', 'a.json'] - >>> # Looking at the contents (what was actually stored/written) - >>> for k, v in ss.items(): - ... if k != 'a.pkl': - ... print(f"{k}: {v}") - ... else: # need to verify pickle data differently, since printing contents is problematic in doctest - ... assert pickle.loads(v) == data_to_write['a.pkl'] - a.bin: b'abc123' - a.reverse_this: b'abc123' - a.csv: b'event,year\r\n Magna Carta,1215\r\n Guido,1956\r\n' - a.txt: b'this is not a text' - a.json: b'{"str": "field", "int": 42, "float": 3.14, "array": [1, 2], "nested": {"a": 1, "b": 2}}' - - """ - _dflt_outgoing_val_trans_for_key = staticmethod(identity_method) - _outgoing_val_trans_for_key = dflt_outgoing_val_trans_for_key - - def __init__( - self, - incoming_val_trans_for_key=None, - outgoing_val_trans_for_key=None, - dflt_incoming_val_trans=None, - dflt_outgoing_val_trans=None, - func_key=None, - ): - super().__init__( - incoming_val_trans_for_key, dflt_incoming_val_trans, func_key - ) - if outgoing_val_trans_for_key is not None: - self._outgoing_val_trans_for_key = outgoing_val_trans_for_key - if dflt_outgoing_val_trans is not None: - self._dflt_outgoing_val_trans = dflt_outgoing_val_trans - - def __setitem__(self, k, v): - func_key = self._func_key(k) - trans_func = self._outgoing_val_trans_for_key.get( - func_key, self._dflt_outgoing_val_trans_for_key - ) - return super().__setitem__(k, trans_func(v))
- - -# TODO: I'd really like to reuse MiscStoreMixin here! There's a lot of potential. -# If store argument of get_obj was a type instead of an instance, or if MiscReaderMixin was a transformer, if would -# be easier -- but would it make their individual concerns mixed? -
[docs]def set_obj( - k, - v, - store=LocalBinaryStore(path_format=''), - outgoing_val_trans_for_key=imdict(dflt_outgoing_val_trans_for_key), - func_key=lambda k: os.path.splitext(k)[1], -): - """A quick way to get an object, with default... everything (but the key, you know, a clue of what you want)""" - - trans_func = outgoing_val_trans_for_key.get( - func_key(k), dflt_outgoing_val_trans_for_key - ) - store[k] = trans_func(v)
- - -# TODO: I'd really like to reuse MiscReaderMixin here! There's a lot of potential. -# Same comment as above. -
[docs]class MiscGetterAndSetter(MiscGetter): - """ - An object to read and write (and nothing else) to a store (default local) with automatic (de)serialization - according to a property of the key (default: file extension). - - >>> from py2store.misc import set_obj, misc_objs # the function and the object - >>> import json - >>> import os - >>> - >>> pjoin = lambda *p: os.path.join(os.path.expanduser('~'), *p) - >>> - >>> d = {'a': {'b': {'c': [1, 2, 3]}}} - >>> misc_objs[pjoin('tmp.json')] = d - >>> filepath = os.path.expanduser('~/tmp.json') - >>> assert misc_objs[filepath] == d # yep, it's there, and can be retrieved - >>> assert json.load(open(filepath)) == d # in case you don't believe it's an actual json file - >>> - >>> # using pickle - >>> misc_objs[pjoin('tmp.pkl')] = d - >>> assert misc_objs[pjoin('tmp.pkl')] == d - >>> - >>> # using txt - >>> misc_objs[pjoin('tmp.txt')] = 'hello world!' - >>> assert misc_objs[pjoin('tmp.txt')] == 'hello world!' - >>> - >>> # using csv - >>> misc_objs[pjoin('tmp.csv')] = [[1,2,3], ['a','b','c']] - >>> assert misc_objs[pjoin('tmp.csv')] == [['1','2','3'], ['a','b','c']] # yeah, well, not numbers, but you deal with it - >>> - >>> # using bin - ... misc_objs[pjoin('tmp.bin')] = b'let us pretend these are bytes of an audio waveform' - >>> assert misc_objs[pjoin('tmp.bin')] == b'let us pretend these are bytes of an audio waveform' - - """ - - def __init__( - self, - store=LocalBinaryStore(path_format=''), - incoming_val_trans_for_key=imdict(dflt_incoming_val_trans_for_key), - outgoing_val_trans_for_key=imdict(dflt_outgoing_val_trans_for_key), - dflt_incoming_val_trans=identity_method, - func_key=lambda k: os.path.splitext(k)[1], - ): - self.store = store - self.incoming_val_trans_for_key = incoming_val_trans_for_key - self.outgoing_val_trans_for_key = outgoing_val_trans_for_key - self.dflt_incoming_val_trans = dflt_incoming_val_trans - self.func_key = func_key - - def __setitem__(self, k, v): - return set_obj( - k, v, self.store, self.outgoing_val_trans_for_key, self.func_key - )
- - -misc_objs = MiscGetterAndSetter() -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/mixins.html b/docs/_modules/py2store/mixins.html deleted file mode 100644 index 0c3abdb..0000000 --- a/docs/_modules/py2store/mixins.html +++ /dev/null @@ -1,454 +0,0 @@ - - - - - - - - py2store.mixins — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.mixins

-import json
-from py2store.errors import (
-    WritesNotAllowed,
-    DeletionsNotAllowed,
-    OverWritesNotAllowedError,
-)
-
-
-
[docs]class SimpleJsonMixin: - """simple json serialization. - Useful to store and retrieve - """ - - _docsuffix = "Data is assumed to be a JSON string, and is loaded with json.loads and dumped with json.dumps" - - def _obj_of_data(self, data): - return json.loads(data) - - def _data_of_obj(self, obj): - return json.dumps(obj)
- - -
[docs]class IdentityKeysWrapMixin: - """Transparent KeysWrapABC. Often placed in the mro to satisfy the KeysWrapABC need in a neutral way. - This is useful in cases where the keys the persistence functions work with are the same as those you want to work - with. - """ - - def _id_of_key(self, k): - """ - Maps an interface identifier (key) to an internal identifier (_id) that is actually used to perform operations. - Can also perform validation and permission checks. - :param k: interface identifier of some data - :return: internal identifier _id - """ - return k - - def _key_of_id(self, _id): - """ - The inverse of _id_of_key. Maps an internal identifier (_id) to an interface identifier (key) - :param _id: - :return: - """ - return _id
- - -
[docs]class IdentityValsWrapMixin: - """ Transparent ValsWrapABC. Often placed in the mro to satisfy the KeysWrapABC need in a neutral way. - This is useful in cases where the values can be persisted by __setitem__ as is (or the serialization is - handled somewhere in the __setitem__ method. - """ - - def _data_of_obj(self, v): - """ - Serialization of a python object. - :param v: A python object. - :return: The serialization of this object, in a format that can be stored by __getitem__ - """ - return v - - def _obj_of_data(self, data): - """ - Deserialization. The inverse of _data_of_obj. - :param data: Serialized data. - :return: The python object corresponding to this data. - """ - return data
- - -
[docs]class IdentityKvWrapMixin(IdentityKeysWrapMixin, IdentityValsWrapMixin): - """Transparent Keys and Vals Wrap""" - - pass
- - -from functools import partial - -encode_as_utf8 = partial(str, encoding="utf-8") - - -
[docs]class StringKvWrap(IdentityKvWrapMixin): - def _obj_of_data(self, v): - return encode_as_utf8(v)
- - -
[docs]class FilteredKeysMixin: - """ - Filters __iter__ and __contains__ with (the boolean filter function attribute) _key_filt. - """ - - def __iter__(self): - return filter(self._key_filt, super().__iter__()) - - def __contains__(self, k) -> bool: - """ - Check if collection of keys contains k. - Note: This method iterates over all elements of the collection to check if k is present. - Therefore it is not efficient, and in most cases should be overridden with a more efficient version. - :return: True if k is in the collection, and False if not - """ - return self._key_filt(k) and super().__contains__(k)
- - -######################################################################################################################## -# Mixins to disable specific operations - - -
[docs]class ReadOnlyMixin: - """Put this as your first parent class to disallow write/delete operations""" - - def __setitem__(self, k, v): - raise WritesNotAllowed("You can't write with that Store") - - def __delitem__(self, k): - raise DeletionsNotAllowed("You can't delete with that Store") - - def clear(self): - raise DeletionsNotAllowed( - "You can't delete (so definitely not delete all) with that Store" - ) - - def pop(self, k): - raise DeletionsNotAllowed( - "You can't delete (including popping) with that Store" - )
- - -from py2store.util import copy_attrs - - -
[docs]class OverWritesNotAllowedMixin: - """Mixin for only allowing a write to a key if they key doesn't already exist. - Note: Should be before the persister in the MRO. - - >>> class TestPersister(OverWritesNotAllowedMixin, dict): - ... pass - >>> p = TestPersister() - >>> p['foo'] = 'bar' - >>> #p['foo'] = 'bar2' # will raise error - >>> p['foo'] = 'this value should not be stored' # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - py2store.errors.OverWritesNotAllowedError: key foo already exists and cannot be overwritten. - If you really want to write to that key, delete it before writing - >>> p['foo'] # foo is still bar - 'bar' - >>> del p['foo'] - >>> p['foo'] = 'this value WILL be stored' - >>> p['foo'] - 'this value WILL be stored' - """ - - @staticmethod - def wrap(cls): - # TODO: Consider moving to trans and making instances wrappable too - class NoOverWritesClass(OverWritesNotAllowedMixin, cls): - ... - - copy_attrs( - NoOverWritesClass, cls, ("__name__", "__qualname__", "__module__") - ) - return NoOverWritesClass - - def __setitem__(self, k, v): - if self.__contains__(k): - raise OverWritesNotAllowedError( - "key {} already exists and cannot be overwritten. " - "If you really want to write to that key, delete it before writing".format( - k - ) - ) - return super().__setitem__(k, v)
- - -######################################################################################################################## -# Mixins to define mapping methods from others - - -class GetBasedContainerMixin: - def __contains__(self, k) -> bool: - """ - Check if collection of keys contains k. - Note: This method actually fetches the contents for k, returning False if there's a key error trying to do so - Therefore it may not be efficient, and in most cases, a method specific to the case should be used. - :return: True if k is in the collection, and False if not - """ - try: - self.__getitem__(k) - return True - except KeyError: - return False - - -class IterBasedContainerMixin: - def __contains__(self, k) -> bool: - """ - Check if collection of keys contains k. - Note: This method iterates over all elements of the collection to check if k is present. - Therefore it is not efficient, and in most cases should be overridden with a more efficient version. - :return: True if k is in the collection, and False if not - """ - for collection_key in self.__iter__(): - if collection_key == k: - return True - return False # return False if the key wasn't found - - -class IterBasedSizedMixin: - def __len__(self) -> int: - """ - Number of elements in collection of keys. - Note: This method iterates over all elements of the collection and counts them. - Therefore it is not efficient, and in most cases should be overridden with a more efficient version. - :return: The number (int) of elements in the collection of keys. - """ - # TODO: some other means to more quickly count files? - # Note: Found that sum(1 for _ in self.__iter__()) was slower for small, slightly faster for big inputs. - count = 0 - for _ in self.__iter__(): - count += 1 - return count - - -
[docs]class IterBasedSizedContainerMixin( - IterBasedSizedMixin, IterBasedContainerMixin -): - """ - An ABC that defines - (a) how to iterate over a collection of elements (keys) (__iter__) - (b) check that a key is contained in the collection (__contains__), and - (c) how to get the number of elements in the collection - This is exactly what the collections.abc.Collection (from which Keys inherits) does. - The difference here, besides the "Keys" purpose-explicit name, is that Keys offers default - __len__ and __contains__ definitions based on what ever __iter__ the concrete class defines. - - Keys is a collection (i.e. a Sized (has __len__), Iterable (has __iter__), Container (has __contains__). - It's purpose is to serve as a collection of object identifiers in a key->obj mapping. - The Keys class doesn't implement __iter__ (so needs to be subclassed with a concrete class), but - offers mixin __len__ and __contains__ methods based on a given __iter__ method. - Note that usually __len__ and __contains__ should be overridden to more, context-dependent, efficient methods. - """ - - pass
- - -class HashableMixin: - def __hash__(self): - return id(self) - - def __eq__(self, other): - return hash(self) == hash(other) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/parse_format.html b/docs/_modules/py2store/parse_format.html deleted file mode 100644 index 4d6e28e..0000000 --- a/docs/_modules/py2store/parse_format.html +++ /dev/null @@ -1,1615 +0,0 @@ - - - - - - - - py2store.parse_format — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.parse_format

-r"""
-Modified from https://github.com/r1chardj0n3s/parse
-
-Parse strings using a specification based on the Python format() syntax.
-
-   ``parse()`` is the opposite of ``format()``
-
-
-From there it's a simple thing to parse a string:
-
->>> parse("It's {}, I love it!", "It's spam, I love it!")
-<Result ('spam',) {}>
->>> _[0]
-'spam'
-
-Or to search a string for some pattern:
-
->>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n')
-<Result (42,) {}>
-
-Or find all the occurrences of some pattern in a string:
-
->>> ''.join(r.fixed[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>"))
-'the bold text'
-
-If you're going to use the same pattern to match lots of strings you can
-compile it once:
-
->>> p = compile("It's {}, I love it!")
->>> print(p)
-<Parser "It's {}, I love it!">
->>> p.parse("It's spam, I love it!")
-<Result ('spam',) {}>
-
-("compile" is not exported for ``import *`` usage as it would override the
-built-in ``compile()`` function)
-
-The default behaviour is to match strings case insensitively. You may match with
-case by specifying `case_sensitive=True`:
-
->>> parse('SPAM', 'spam', case_sensitive=True) is None
-True
-
-
-Format Syntax
--------------
-
-A basic version of the `Format String Syntax`_ is supported with anonymous
-(fixed-position), named and formatted fields::
-
-   {[field name]:[format spec]}
-
-Field names must be a valid Python identifiers, including dotted names;
-element indexes imply dictionaries (see below for example).
-
-Numbered fields are also not supported: the result of parsing will include
-the parsed fields in the order they are parsed.
-
-The conversion of fields to types other than strings is done based on the
-type in the format specification, which mirrors the ``format()`` behaviour.
-There are no "!" field conversions like ``format()`` has.
-
-Some simple parse() format string examples:
-
->>> parse("Bring me a {}", "Bring me a shrubbery")
-<Result ('shrubbery',) {}>
->>> r = parse("The {} who say {}", "The knights who say Ni!")
->>> print(r)
-<Result ('knights', 'Ni!') {}>
->>> print(r.fixed)
-('knights', 'Ni!')
->>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade")
->>> print(r)
-<Result () {'item': 'hand grenade'}>
->>> print(r.named)
-{'item': 'hand grenade'}
->>> print(r['item'])
-hand grenade
-
-Dotted names and indexes are possible though the application must make
-additional sense of the result:
-
->>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
->>> print(r)
-<Result () {'food.type': 'spam'}>
->>> print(r.named)
-{'food.type': 'spam'}
->>> print(r['food.type'])
-spam
->>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!")
->>> print(r)
-<Result () {'quest': {'name': 'to seek the holy grail!'}}>
->>> print(r['quest'])
-{'name': 'to seek the holy grail!'}
->>> print(r['quest']['name'])
-to seek the holy grail!
-
-If the text you're matching has braces in it you can match those by including
-a double-brace ``{{`` or ``}}`` in your format string, just like format() does.
-
-
-Format Specification
---------------------
-
-Most often a straight format-less ``{}`` will suffice where a more complex
-format specification might have been used.
-
-Most of `format()`'s `Format Specification Mini-Language`_ is supported:
-
-   [[fill]align][0][width][.precision][type]
-
-The differences between `parse()` and `format()` are:
-
-- The align operators will cause spaces (or specified fill character) to be
-  stripped from the parsed value. The width is not enforced; it just indicates
-  there may be whitespace or "0"s to strip.
-- Numeric parsing will automatically handle a "0b", "0o" or "0x" prefix.
-  That is, the "#" format character is handled automatically by d, b, o
-  and x formats. For "d" any will be accepted, but for the others the correct
-  prefix must be present if at all.
-- Numeric sign is handled automatically.
-- The thousands separator is handled automatically if the "n" type is used.
-- The types supported are a slightly different mix to the format() types.  Some
-  format() types come directly over: "d", "n", "%", "f", "e", "b", "o" and "x".
-  In addition some regular expression character group types "D", "w", "W", "s"
-  and "S" are also available.
-- The "e" and "g" types are case-insensitive so there is not need for
-  the "E" or "G" types.
-
-===== =========================================== ========
-Type  Characters Matched                          Output
-===== =========================================== ========
- w    Letters and underscore                      str
- W    Non-letter and underscore                   str
- s    Whitespace                                  str
- S    Non-whitespace                              str
- d    Digits (effectively integer numbers)        int
- D    Non-digit                                   str
- n    Numbers with thousands separators (, or .)  int
- %    Percentage (converted to value/100.0)       float
- f    Fixed-point numbers                         float
- F    Decimal numbers                             Decimal
- e    Floating-point numbers with exponent        float
-      e.g. 1.1e-10, NAN (all case insensitive)
- g    General number format (either d, f or e)    float
- b    Binary numbers                              int
- o    Octal numbers                               int
- x    Hexadecimal numbers (lower and upper case)  int
- ti   ISO 8601 format date/time                   datetime
-      e.g. 1972-01-20T10:21:36Z ("T" and "Z"
-      optional)
- te   RFC2822 e-mail format date/time             datetime
-      e.g. Mon, 20 Jan 1972 10:21:36 +1000
- tg   Global (day/month) format date/time         datetime
-      e.g. 20/1/1972 10:21:36 AM +1:00
- ta   US (month/day) format date/time             datetime
-      e.g. 1/20/1972 10:21:36 PM +10:30
- tc   ctime() format date/time                    datetime
-      e.g. Sun Sep 16 01:03:52 1973
- th   HTTP log format date/time                   datetime
-      e.g. 21/Nov/2011:00:07:11 +0000
- ts   Linux system log format date/time           datetime
-      e.g. Nov  9 03:37:44
- tt   Time                                        time
-      e.g. 10:21:36 PM -5:30
-===== =========================================== ========
-
-Some examples of typed parsing with ``None`` returned if the typing
-does not match:
-
->>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...')
-<Result (3, 'weapons') {}>
->>> parse('Our {:d} {:w} are...', 'Our three weapons are...')
->>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM')
-<Result (datetime.datetime(2011, 2, 1, 23, 0),) {}>
-
-And messing about with alignment:
-
->>> parse('with {:>} herring', 'with     a herring')
-<Result ('a',) {}>
->>> parse('spam {:^} spam', 'spam    lovely     spam')
-<Result ('lovely',) {}>
-
-Note that the "center" alignment does not test to make sure the value is
-centered - it just strips leading and trailing whitespace.
-
-Width and precision may be used to restrict the size of matched text
-from the input. Width specifies a minimum size and precision specifies
-a maximum. For example:
-
->>> parse('{:.2}{:.2}', 'look')           # specifying precision
-<Result ('lo', 'ok') {}>
->>> parse('{:4}{:4}', 'look at that')     # specifying width
-<Result ('look', 'at that') {}>
->>> parse('{:4}{:.4}', 'look at that')    # specifying both
-<Result ('look at ', 'that') {}>
->>> parse('{:2d}{:2d}', '0440')           # parsing two contiguous numbers
-<Result (4, 40) {}>
-
-Some notes for the date and time types:
-
-- the presence of the time part is optional (including ISO 8601, starting
-  at the "T"). A full datetime object will always be returned; the time
-  will be set to 00:00:00. You may also specify a time without seconds.
-- when a seconds amount is present in the input fractions will be parsed
-  to give microseconds.
-- except in ISO 8601 the day and month digits may be 0-padded.
-- the date separator for the tg and ta formats may be "-" or "/".
-- named months (abbreviations or full names) may be used in the ta and tg
-  formats in place of numeric months.
-- as per RFC 2822 the e-mail format may omit the day (and comma), and the
-  seconds but nothing else.
-- hours greater than 12 will be happily accepted.
-- the AM/PM are optional, and if PM is found then 12 hours will be added
-  to the datetime object's hours amount - even if the hour is greater
-  than 12 (for consistency.)
-- in ISO 8601 the "Z" (UTC) timezone part may be a numeric offset
-- timezones are specified as "+HH:MM" or "-HH:MM". The hour may be one or two
-  digits (0-padded is OK.) Also, the ":" is optional.
-- the timezone is optional in all except the e-mail format (it defaults to
-  UTC.)
-- named timezones are not handled yet.
-
-Note: attempting to match too many datetime fields in a single parse() will
-currently result in a resource allocation issue. A TooManyFields exception
-will be raised in this instance. The current limit is about 15. It is hoped
-that this limit will be removed one day.
-
-.. _`Format String Syntax`:
-  http://docs.python.org/library/string.html#format-string-syntax
-.. _`Format Specification Mini-Language`:
-  http://docs.python.org/library/string.html#format-specification-mini-language
-
-
-Result and Match Objects
-------------------------
-
-The result of a ``parse()`` and ``search()`` operation is either ``None`` (no match), a
-``Result`` instance or a ``Match`` instance if ``evaluate_result`` is False.
-
-The ``Result`` instance has three attributes:
-
-fixed
-   A tuple of the fixed-position, anonymous fields extracted from the input.
-named
-   A dictionary of the named fields extracted from the input.
-spans
-   A dictionary mapping the names and fixed position indices matched to a
-   2-tuple slice range of where the match occurred in the input.
-   The span does not include any stripped padding (alignment or width).
-
-The ``Match`` instance has one method:
-
-evaluate_result()
-   Generates and returns a ``Result`` instance for this ``Match`` object.
-
-
-
-Custom Type Conversions
------------------------
-
-If you wish to have matched fields automatically converted to your own type you
-may pass in a dictionary of type conversion information to ``parse()`` and
-``compile()``.
-
-The converter will be passed the field string matched. Whatever it returns
-will be substituted in the ``Result`` instance for that field.
-
-Your custom type conversions may override the builtin types if you supply one
-with the same identifier.
-
->>> def shouty(string):
-...    return string.upper()
-...
->>> parse('{:shouty} world', 'hello world', dict(shouty=shouty))
-<Result ('HELLO',) {}>
-
-If the type converter has the optional ``pattern`` attribute, it is used as
-regular expression for better pattern matching (instead of the default one).
-
->>> def parse_number(text):
-...    return int(text)
->>> parse_number.pattern = r'\d+'
->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
-<Result () {'number': 42}>
->>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number))
->>> assert _ is None, "MISMATCH"
-
-You can also use the ``with_pattern(pattern)`` decorator to add this
-information to a type converter function:
-
->>> @with_pattern(r'\d+')
-... def parse_number(text):
-...    return int(text)
->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
-<Result () {'number': 42}>
-
-A more complete example of a custom type might be:
-
->>> yesno_mapping = {
-...     "yes":  True,   "no":    False,
-...     "on":   True,   "off":   False,
-...     "true": True,   "false": False,
-... }
->>> @with_pattern(r"|".join(yesno_mapping))
-... def parse_yesno(text):
-...     return yesno_mapping[text.lower()]
-
-
-If the type converter ``pattern`` uses regex-grouping (with parenthesis),
-you should indicate this by using the optional ``regex_group_count`` parameter
-in the ``with_pattern()`` decorator:
-
->>> @with_pattern(r'((\d+))', regex_group_count=2)
-... def parse_number2(text):
-...    return int(text)
->>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
-<Result (42, 43) {}>
-
-Otherwise, this may cause parsing problems with unnamed/fixed parameters.
-
-
-Potential Gotchas
------------------
-
-`parse()` will always match the shortest text necessary (from left to right)
-to fulfil the parse pattern, so for example:
-
->>> pattern = '{dir1}/{dir2}'
->>> data = 'root/parent/subdir'
->>> sorted(parse(pattern, data).named.items())
-[('dir1', 'root'), ('dir2', 'parent/subdir')]
-
-So, even though `{'dir1': 'root/parent', 'dir2': 'subdir'}` would also fit
-the pattern, the actual match represents the shortest successful match for
-`dir1`.
-
-----
-
-**Version history (in brief)**:
-
-- 1.9.0 We now honor precision and width specifiers when parsing numbers
-  and strings, allowing parsing of concatenated elements of fixed width
-  (thanks Julia Signell)
-- 1.8.4 Add LICENSE file at request of packagers.
-  Correct handling of AM/PM to follow most common interpretation.
-  Correct parsing of hexadecimal that looks like a binary prefix.
-  Add ability to parse case sensitively.
-  Add parsing of numbers to Decimal with "F" (thanks John Vandenberg)
-- 1.8.3 Add regex_group_count to with_pattern() decorator to support
-  user-defined types that contain brackets/parenthesis (thanks Jens Engel)
-- 1.8.2 add documentation for including braces in format string
-- 1.8.1 ensure bare hexadecimal digits are not matched
-- 1.8.0 support manual control over result evaluation (thanks Timo Furrer)
-- 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow
-  more than 100 re groups in Python 3.5+ (thanks David King)
-- 1.6.6 parse Linux system log dates (thanks Alex Cowan)
-- 1.6.5 handle precision in float format (thanks Levi Kilcher)
-- 1.6.4 handle pipe "|" characters in parse string (thanks Martijn Pieters)
-- 1.6.3 handle repeated instances of named fields, fix bug in PM time
-  overflow
-- 1.6.2 fix logging to use local, not root logger (thanks Necku)
-- 1.6.1 be more flexible regarding matched ISO datetimes and timezones in
-  general, fix bug in timezones without ":" and improve docs
-- 1.6.0 add support for optional ``pattern`` attribute in user-defined types
-  (thanks Jens Engel)
-- 1.5.3 fix handling of question marks
-- 1.5.2 fix type conversion error with dotted names (thanks Sebastian Thiel)
-- 1.5.1 implement handling of named datetime fields
-- 1.5 add handling of dotted field names (thanks Sebastian Thiel)
-- 1.4.1 fix parsing of "0" in int conversion (thanks James Rowe)
-- 1.4 add __getitem__ convenience access on Result.
-- 1.3.3 fix Python 2.5 setup.py issue.
-- 1.3.2 fix Python 3.2 setup.py issue.
-- 1.3.1 fix a couple of Python 3.2 compatibility issues.
-- 1.3 added search() and findall(); removed compile() from ``import *``
-  export as it overwrites builtin.
-- 1.2 added ability for custom and override type conversions to be
-  provided; some cleanup
-- 1.1.9 to keep things simpler number sign is handled automatically;
-  significant robustification in the face of edge-case input.
-- 1.1.8 allow "d" fields to have number base "0x" etc. prefixes;
-  fix up some field type interactions after stress-testing the parser;
-  implement "%" type.
-- 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported).
-- 1.1.6 add "e" and "g" field types; removed redundant "h" and "X";
-  removed need for explicit "#".
-- 1.1.5 accept textual dates in more places; Result now holds match span
-  positions.
-- 1.1.4 fixes to some int type conversion; implemented "=" alignment; added
-  date/time parsing with a variety of formats handled.
-- 1.1.3 type conversion is automatic based on specified field types. Also added
-  "f" and "n" types.
-- 1.1.2 refactored, added compile() and limited ``from parse import *``
-- 1.1.1 documentation improvements
-- 1.1.0 implemented more of the `Format Specification Mini-Language`_
-  and removed the restriction on mixing fixed-position and named fields
-- 1.0.0 initial release
-
-This code is copyright 2012-2017 Richard Jones <richard@python.org>
-See the end of the source file for the license of use.
-"""
-
-from __future__ import absolute_import
-
-__version__ = '1.9.0'
-
-# yes, I now have two problems
-import re
-import sys
-from datetime import datetime, time, tzinfo, timedelta
-from decimal import Decimal
-from functools import partial
-import logging
-
-__all__ = 'parse search findall with_pattern'.split()
-
-log = logging.getLogger(__name__)
-
-
-
[docs]def with_pattern(pattern, regex_group_count=None): - r"""Attach a regular expression pattern matcher to a custom type converter - function. - - This annotates the type converter with the :attr:`pattern` attribute. - - EXAMPLE: - >>> @with_pattern(r"\d+") - ... def parse_number(text): - ... return int(text) - - is equivalent to: - - >>> def parse_number(text): - ... return int(text) - >>> parse_number.pattern = r"\d+" - - :param pattern: regular expression pattern (as text) - :param regex_group_count: Indicates how many regex-groups are in pattern. - :return: wrapped function - """ - - def decorator(func): - func.pattern = pattern - func.regex_group_count = regex_group_count - return func - - return decorator
- - -def int_convert(base): - """Convert a string to an integer. - - The string may start with a sign. - - It may be of a base other than 10. - - If may start with a base indicator, 0#nnnn, which we assume should - override the specified base. - - It may also have other non-numeric characters that we can ignore. - """ - CHARS = '0123456789abcdefghijklmnopqrstuvwxyz' - - def f(string, match, base=base): - if string[0] == '-': - sign = -1 - else: - sign = 1 - - if string[0] == '0' and len(string) > 2: - if string[1] in 'bB': - base = 2 - elif string[1] in 'oO': - base = 8 - elif string[1] in 'xX': - base = 16 - else: - # just go with the base specifed - pass - - chars = CHARS[:base] - string = re.sub('[^%s]' % chars, '', string.lower()) - return sign * int(string, base) - - return f - - -def percentage(string, match): - return float(string[:-1]) / 100.0 - - -class FixedTzOffset(tzinfo): - """Fixed offset in minutes east from UTC. - """ - - ZERO = timedelta(0) - - def __init__(self, offset, name): - self._offset = timedelta(minutes=offset) - self._name = name - - def __repr__(self): - return '<%s %s %s>' % ( - self.__class__.__name__, - self._name, - self._offset, - ) - - def utcoffset(self, dt): - return self._offset - - def tzname(self, dt): - return self._name - - def dst(self, dt): - return self.ZERO - - def __eq__(self, other): - return self._name == other._name and self._offset == other._offset - - -MONTHS_MAP = dict( - Jan=1, - January=1, - Feb=2, - February=2, - Mar=3, - March=3, - Apr=4, - April=4, - May=5, - Jun=6, - June=6, - Jul=7, - July=7, - Aug=8, - August=8, - Sep=9, - September=9, - Oct=10, - October=10, - Nov=11, - November=11, - Dec=12, - December=12, -) -DAYS_PAT = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)' -MONTHS_PAT = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)' -ALL_MONTHS_PAT = '(%s)' % '|'.join(MONTHS_MAP) -TIME_PAT = r'(\d{1,2}:\d{1,2}(:\d{1,2}(\.\d+)?)?)' -AM_PAT = r'(\s+[AP]M)' -TZ_PAT = r'(\s+[-+]\d\d?:?\d\d)' - - -def date_convert( - string, - match, - ymd=None, - mdy=None, - dmy=None, - d_m_y=None, - hms=None, - am=None, - tz=None, - mm=None, - dd=None, -): - """Convert the incoming string containing some date / time info into a - datetime instance. - """ - groups = match.groups() - time_only = False - if mm and dd: - y = datetime.today().year - m = groups[mm] - d = groups[dd] - elif ymd is not None: - y, m, d = re.split(r'[-/\s]', groups[ymd]) - elif mdy is not None: - m, d, y = re.split(r'[-/\s]', groups[mdy]) - elif dmy is not None: - d, m, y = re.split(r'[-/\s]', groups[dmy]) - elif d_m_y is not None: - d, m, y = d_m_y - d = groups[d] - m = groups[m] - y = groups[y] - else: - time_only = True - - H = M = S = u = 0 - if hms is not None and groups[hms]: - t = groups[hms].split(':') - if len(t) == 2: - H, M = t - else: - H, M, S = t - if '.' in S: - S, u = S.split('.') - u = int(float('.' + u) * 1000000) - S = int(S) - H = int(H) - M = int(M) - - if am is not None: - am = groups[am] - if am: - am = am.strip() - if am == 'AM' and H == 12: - # correction for "12" hour functioning as "0" hour: 12:15 AM = 00:15 by 24 hr clock - H -= 12 - elif am == 'PM' and H == 12: - # no correction needed: 12PM is midday, 12:00 by 24 hour clock - pass - elif am == 'PM': - H += 12 - - if tz is not None: - tz = groups[tz] - if tz == 'Z': - tz = FixedTzOffset(0, 'UTC') - elif tz: - tz = tz.strip() - if tz.isupper(): - # TODO use the awesome python TZ module? - pass - else: - sign = tz[0] - if ':' in tz: - tzh, tzm = tz[1:].split(':') - elif len(tz) == 4: # 'snnn' - tzh, tzm = tz[1], tz[2:4] - else: - tzh, tzm = tz[1:3], tz[3:5] - offset = int(tzm) + int(tzh) * 60 - if sign == '-': - offset = -offset - tz = FixedTzOffset(offset, tz) - - if time_only: - d = time(H, M, S, u, tzinfo=tz) - else: - y = int(y) - if m.isdigit(): - m = int(m) - else: - m = MONTHS_MAP[m] - d = int(d) - d = datetime(y, m, d, H, M, S, u, tzinfo=tz) - - return d - - -class TooManyFields(ValueError): - pass - - -class RepeatedNameError(ValueError): - pass - - -# note: {} are handled separately -# note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit -REGEX_SAFETY = re.compile(r'([?\\\\.[\]()*+\^$!\|])') - -# allowed field types -ALLOWED_TYPES = set(list('nbox%fFegwWdDsS') + ['t' + c for c in 'ieahgcts']) - - -def extract_format(format, extra_types): - """Pull apart the format [[fill]align][0][width][.precision][type] - """ - fill = align = None - if format[0] in '<>=^': - align = format[0] - format = format[1:] - elif len(format) > 1 and format[1] in '<>=^': - fill = format[0] - align = format[1] - format = format[2:] - - zero = False - if format and format[0] == '0': - zero = True - format = format[1:] - - width = '' - while format: - if not format[0].isdigit(): - break - width += format[0] - format = format[1:] - - if format.startswith('.'): - # Precision isn't needed but we need to capture it so that - # the ValueError isn't raised. - format = format[1:] # drop the '.' - precision = '' - while format: - if not format[0].isdigit(): - break - precision += format[0] - format = format[1:] - - # the rest is the type, if present - type = format - if type and type not in ALLOWED_TYPES and type not in extra_types: - raise ValueError('format spec %r not recognised' % type) - - return locals() - - -PARSE_RE = re.compile( - r'''({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})''' -) - - -class Parser(object): - """Encapsulate a format string that may be used to parse other strings. - """ - - def __init__(self, format, extra_types=None, case_sensitive=False): - # a mapping of a name as in {hello.world} to a regex-group compatible - # name, like hello__world Its used to prevent the transformation of - # name-to-group and group to name to fail subtly, such as in: - # hello_.world-> hello___world->hello._world - self._group_to_name_map = {} - # also store the original field name to group name mapping to allow - # multiple instances of a name in the format string - self._name_to_group_map = {} - # and to sanity check the repeated instances store away the first - # field type specification for the named field - self._name_types = {} - - self._format = format - if extra_types is None: - extra_types = {} - self._extra_types = extra_types - if case_sensitive: - self._re_flags = re.DOTALL - else: - self._re_flags = re.IGNORECASE | re.DOTALL - self._fixed_fields = [] - self._named_fields = [] - self._group_index = 0 - self._type_conversions = {} - self._expression = self._generate_expression() - self.__search_re = None - self.__match_re = None - - log.debug('format %r -> %r', format, self._expression) - - def __repr__(self): - if len(self._format) > 20: - return '<%s %r>' % ( - self.__class__.__name__, - self._format[:17] + '...', - ) - return '<%s %r>' % (self.__class__.__name__, self._format) - - @property - def _search_re(self): - if self.__search_re is None: - try: - self.__search_re = re.compile(self._expression, self._re_flags) - except AssertionError: - # access error through sys to keep py3k and backward compat - e = str(sys.exc_info()[1]) - if e.endswith('this version only supports 100 named groups'): - raise TooManyFields( - 'sorry, you are attempting to parse ' - 'too many complex fields' - ) - return self.__search_re - - @property - def _match_re(self): - if self.__match_re is None: - expression = '^%s$' % self._expression - try: - self.__match_re = re.compile(expression, self._re_flags) - except AssertionError: - # access error through sys to keep py3k and backward compat - e = str(sys.exc_info()[1]) - if e.endswith('this version only supports 100 named groups'): - raise TooManyFields( - 'sorry, you are attempting to parse ' - 'too many complex fields' - ) - except re.error: - raise NotImplementedError( - 'Group names (e.g. (?P<name>) can ' - "cause failure, as they are not escaped properly: '%s'" - % expression - ) - return self.__match_re - - def parse(self, string, evaluate_result=True): - """Match my format to the string exactly. - - Return a Result or Match instance or None if there's no match. - """ - m = self._match_re.match(string) - if m is None: - return None - - if evaluate_result: - return self.evaluate_result(m) - else: - return Match(self, m) - - def search(self, string, pos=0, endpos=None, evaluate_result=True): - """Search the string for my format. - - Optionally start the search at "pos" character index and limit the - search to a maximum index of endpos - equivalent to - search(string[:endpos]). - - If the ``evaluate_result`` argument is set to ``False`` a - Match instance is returned instead of the actual Result instance. - - Return either a Result instance or None if there's no match. - """ - if endpos is None: - endpos = len(string) - m = self._search_re.search(string, pos, endpos) - if m is None: - return None - - if evaluate_result: - return self.evaluate_result(m) - else: - return Match(self, m) - - def findall( - self, - string, - pos=0, - endpos=None, - extra_types=None, - evaluate_result=True, - ): - """Search "string" for all occurrences of "format". - - Optionally start the search at "pos" character index and limit the - search to a maximum index of endpos - equivalent to - search(string[:endpos]). - - Returns an iterator that holds Result or Match instances for each format match - found. - """ - if endpos is None: - endpos = len(string) - return ResultIterator( - self, string, pos, endpos, evaluate_result=evaluate_result - ) - - def _expand_named_fields(self, named_fields): - result = {} - for field, value in named_fields.items(): - # split 'aaa[bbb][ccc]...' into 'aaa' and '[bbb][ccc]...' - basename, subkeys = re.match(r'([^\[]+)(.*)', field).groups() - - # create nested dictionaries {'aaa': {'bbb': {'ccc': ...}}} - d = result - k = basename - - if subkeys: - for subkey in re.findall(r'\[[^\]]+\]', subkeys): - d = d.setdefault(k, {}) - k = subkey[1:-1] - - # assign the value to the last key - d[k] = value - - return result - - def evaluate_result(self, m): - """Generate a Result instance for the given regex match object""" - # ok, figure the fixed fields we've pulled out and type convert them - fixed_fields = list(m.groups()) - for n in self._fixed_fields: - if n in self._type_conversions: - fixed_fields[n] = self._type_conversions[n](fixed_fields[n], m) - fixed_fields = tuple(fixed_fields[n] for n in self._fixed_fields) - - # grab the named fields, converting where requested - groupdict = m.groupdict() - named_fields = {} - name_map = {} - for k in self._named_fields: - korig = self._group_to_name_map[k] - name_map[korig] = k - if k in self._type_conversions: - value = self._type_conversions[k](groupdict[k], m) - else: - value = groupdict[k] - - named_fields[korig] = value - - # now figure the match spans - spans = dict((n, m.span(name_map[n])) for n in named_fields) - spans.update( - (i, m.span(n + 1)) for i, n in enumerate(self._fixed_fields) - ) - - # and that's our result - return Result( - fixed_fields, self._expand_named_fields(named_fields), spans - ) - - def _regex_replace(self, match): - return '\\' + match.group(1) - - def _generate_expression(self): - # turn my _format attribute into the _expression attribute - e = [] - for part in PARSE_RE.split(self._format): - if not part: - continue - elif part == '{{': - e.append(r'\{') - elif part == '}}': - e.append(r'\}') - elif part[0] == '{': - # this will be a braces-delimited field to handle - e.append(self._handle_field(part)) - else: - # just some text to match - e.append(REGEX_SAFETY.sub(self._regex_replace, part)) - return ''.join(e) - - def _to_group_name(self, field): - # return a version of field which can be used as capture group, even - # though it might contain '.' - group = field.replace('.', '_').replace('[', '_').replace(']', '_') - - # make sure we don't collide ("a.b" colliding with "a_b") - n = 1 - while group in self._group_to_name_map: - n += 1 - if '.' in field: - group = field.replace('.', '_' * n) - elif '_' in field: - group = field.replace('_', '_' * n) - else: - raise KeyError('duplicated group name %r' % (field,)) - - # save off the mapping - self._group_to_name_map[group] = field - self._name_to_group_map[field] = group - return group - - def _handle_field(self, field): - # first: lose the braces - field = field[1:-1] - - # now figure whether this is an anonymous or named field, and whether - # there's any format specification - format = '' - if field and field[0].isalpha(): - if ':' in field: - name, format = field.split(':') - else: - name = field - if name in self._name_to_group_map: - if self._name_types[name] != format: - raise RepeatedNameError( - 'field type %r for field "%s" ' - 'does not match previous seen type %r' - % (format, name, self._name_types[name]) - ) - group = self._name_to_group_map[name] - # match previously-seen value - return '(?P=%s)' % group - else: - group = self._to_group_name(name) - self._name_types[name] = format - self._named_fields.append(group) - # this will become a group, which must not contain dots - wrap = '(?P<%s>%%s)' % group - else: - self._fixed_fields.append(self._group_index) - wrap = '(%s)' - if ':' in field: - format = field[1:] - group = self._group_index - - # simplest case: no type specifier ({} or {name}) - if not format: - self._group_index += 1 - return wrap % '.+?' - - # decode the format specification - format = extract_format(format, self._extra_types) - - # figure type conversions, if any - type = format['type'] - is_numeric = type and type in 'n%fegdobh' - if type in self._extra_types: - type_converter = self._extra_types[type] - s = getattr(type_converter, 'pattern', r'.+?') - regex_group_count = getattr(type_converter, 'regex_group_count', 0) - if regex_group_count is None: - regex_group_count = 0 - self._group_index += regex_group_count - - def f(string, m): - return type_converter(string) - - self._type_conversions[group] = f - elif type == 'n': - s = r'\d{1,3}([,.]\d{3})*' - self._group_index += 1 - self._type_conversions[group] = int_convert(10) - elif type == 'b': - s = '(0[bB])?[01]+' - self._type_conversions[group] = int_convert(2) - self._group_index += 1 - elif type == 'o': - s = '(0[oO])?[0-7]+' - self._type_conversions[group] = int_convert(8) - self._group_index += 1 - elif type == 'x': - s = '(0[xX])?[0-9a-fA-F]+' - self._type_conversions[group] = int_convert(16) - self._group_index += 1 - elif type == '%': - s = r'\d+(\.\d+)?%' - self._group_index += 1 - self._type_conversions[group] = percentage - elif type == 'f': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'F': - s = r'\d+\.\d+' - self._type_conversions[group] = lambda s, m: Decimal(s) - elif type == 'e': - s = r'\d+\.\d+[eE][-+]?\d+|nan|NAN|[-+]?inf|[-+]?INF' - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'g': - s = r'\d+(\.\d+)?([eE][-+]?\d+)?|nan|NAN|[-+]?inf|[-+]?INF' - self._group_index += 2 - self._type_conversions[group] = lambda s, m: float(s) - elif type == 'd': - if format.get('width'): - width = '{1,%s}' % int(format['width']) - else: - width = '+' - s = '\\d{w}|0[xX][0-9a-fA-F]{w}|0[bB][01]{w}|0[oO][0-7]{w}'.format( - w=width - ) - self._type_conversions[group] = int_convert(10) - elif type == 'ti': - s = ( - r'(\d{4}-\d\d-\d\d)((\s+|T)%s)?(Z|\s*[-+]\d\d:?\d\d)?' - % TIME_PAT - ) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, ymd=n + 1, hms=n + 4, tz=n + 7 - ) - self._group_index += 7 - elif type == 'tg': - s = r'(\d{1,2}[-/](\d{1,2}|%s)[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, - TIME_PAT, - AM_PAT, - TZ_PAT, - ) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, dmy=n + 1, hms=n + 5, am=n + 8, tz=n + 9 - ) - self._group_index += 9 - elif type == 'ta': - s = r'((\d{1,2}|%s)[-/]\d{1,2}[-/]\d{4})(\s+%s)?%s?%s?' % ( - ALL_MONTHS_PAT, - TIME_PAT, - AM_PAT, - TZ_PAT, - ) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, mdy=n + 1, hms=n + 5, am=n + 8, tz=n + 9 - ) - self._group_index += 9 - elif type == 'te': - # this will allow microseconds through if they're present, but meh - s = r'(%s,\s+)?(\d{1,2}\s+%s\s+\d{4})\s+%s%s' % ( - DAYS_PAT, - MONTHS_PAT, - TIME_PAT, - TZ_PAT, - ) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, dmy=n + 3, hms=n + 5, tz=n + 8 - ) - self._group_index += 8 - elif type == 'th': - # slight flexibility here from the stock Apache format - s = r'(\d{1,2}[-/]%s[-/]\d{4}):%s%s' % ( - MONTHS_PAT, - TIME_PAT, - TZ_PAT, - ) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, dmy=n + 1, hms=n + 3, tz=n + 6 - ) - self._group_index += 6 - elif type == 'tc': - s = r'(%s)\s+%s\s+(\d{1,2})\s+%s\s+(\d{4})' % ( - DAYS_PAT, - MONTHS_PAT, - TIME_PAT, - ) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, d_m_y=(n + 4, n + 3, n + 8), hms=n + 5 - ) - self._group_index += 8 - elif type == 'tt': - s = r'%s?%s?%s?' % (TIME_PAT, AM_PAT, TZ_PAT) - n = self._group_index - self._type_conversions[group] = partial( - date_convert, hms=n + 1, am=n + 4, tz=n + 5 - ) - self._group_index += 5 - elif type == 'ts': - s = r'%s(\s+)(\d+)(\s+)(\d{1,2}:\d{1,2}:\d{1,2})?' % MONTHS_PAT - n = self._group_index - self._type_conversions[group] = partial( - date_convert, mm=n + 1, dd=n + 3, hms=n + 5 - ) - self._group_index += 5 - - elif type: - s = r'\%s+' % type - elif format.get('precision'): - if format.get('width'): - s = '.{%s,%s}?' % (format['width'], format['precision']) - else: - s = '.{1,%s}?' % format['precision'] - elif format.get('width'): - s = '.{%s,}?' % format['width'] - else: - s = '.+?' - - align = format['align'] - fill = format['fill'] - - # handle some numeric-specific things like fill and sign - if is_numeric: - # prefix with something (align "=" trumps zero) - if align == '=': - # special case - align "=" acts like the zero above but with - # configurable fill defaulting to "0" - if not fill: - fill = '0' - s = '%s*' % fill + s - - # allow numbers to be prefixed with a sign - s = r'[-+ ]?' + s - - if not fill: - fill = ' ' - - # Place into a group now - this captures the value we want to keep. - # Everything else from now is just padding to be stripped off - if wrap: - s = wrap % s - self._group_index += 1 - - if format['width']: - # all we really care about is that if the format originally - # specified a width then there will probably be padding - without - # an explicit alignment that'll mean right alignment with spaces - # padding - if not align: - align = '>' - - if fill in r'.\+?*[](){}^$': - fill = '\\' + fill - - # align "=" has been handled - if align == '<': - s = '%s%s*' % (s, fill) - elif align == '>': - s = '%s*%s' % (fill, s) - elif align == '^': - s = '%s*%s%s*' % (fill, s, fill) - - return s - - -class Result(object): - """The result of a parse() or search(). - - Fixed results may be looked up using result[index]. Named results may be - looked up using result['name']. - """ - - def __init__(self, fixed, named, spans): - self.fixed = fixed - self.named = named - self.spans = spans - - def __getitem__(self, item): - if isinstance(item, int): - return self.fixed[item] - return self.named[item] - - def __repr__(self): - return '<%s %r %r>' % (self.__class__.__name__, self.fixed, self.named) - - -class Match(object): - """The result of a parse() or search() if no results are generated. - - This class is only used to expose internal used regex match objects - to the user and use them for external Parser.evaluate_result calls. - """ - - def __init__(self, parser, match): - self.parser = parser - self.match = match - - def evaluate_result(self): - """Generate results for this Match""" - return self.parser.evaluate_result(self.match) - - -class ResultIterator(object): - """The result of a findall() operation. - - Each element is a Result instance. - """ - - def __init__(self, parser, string, pos, endpos, evaluate_result=True): - self.parser = parser - self.string = string - self.pos = pos - self.endpos = endpos - self.evaluate_result = evaluate_result - - def __iter__(self): - return self - - def __next__(self): - m = self.parser._search_re.search(self.string, self.pos, self.endpos) - if m is None: - raise StopIteration() - self.pos = m.end() - - if self.evaluate_result: - return self.parser.evaluate_result(m) - else: - return Match(self.parser, m) - - # pre-py3k compat - next = __next__ - - -
[docs]def parse( - format, - string, - extra_types=None, - evaluate_result=True, - case_sensitive=False, -): - """Using "format" attempt to pull values from "string". - - The format must match the string contents exactly. If the value - you're looking for is instead just a part of the string use - search(). - - If ``evaluate_result`` is True the return value will be an Result instance with two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False the return value will be a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - - In the case there is no match parse() will return None. - """ - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return p.parse(string, evaluate_result=evaluate_result)
- - - - - -
[docs]def findall( - format, - string, - pos=0, - endpos=None, - extra_types=None, - evaluate_result=True, - case_sensitive=False, -): - """Search "string" for all occurrences of "format". - - You will be returned an iterator that holds Result instances - for each format match found. - - Optionally start the search at "pos" character index and limit the search - to a maximum index of endpos - equivalent to search(string[:endpos]). - - If ``evaluate_result`` is True each returned Result instance has two attributes: - - .fixed - tuple of fixed-position values from the string - .named - dict of named values from the string - - If ``evaluate_result`` is False each returned value is a Match instance with one method: - - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - If the format is invalid a ValueError will be raised. - - See the module documentation for the use of "extra_types". - """ - p = Parser(format, extra_types=extra_types, case_sensitive=case_sensitive) - return Parser(format, extra_types=extra_types).findall( - string, pos, endpos, evaluate_result=evaluate_result - )
- - -def compile(format, extra_types=None, case_sensitive=False): - """Create a Parser instance to parse "format". - - The resultant Parser has a method .parse(string) which - behaves in the same manner as parse(format, string). - - The default behaviour is to match strings case insensitively. You may match with - case by specifying case_sensitive=True. - - Use this function if you intend to parse many strings - with the same format. - - See the module documentation for the use of "extra_types". - - Returns a Parser instance. - """ - return Parser(format, extra_types=extra_types) - - -def match_re_for_fstring(fstring): - return compile(fstring)._match_re - - -def search_re_for_fstring(fstring): - return compile(fstring)._search_re - - -# Modified from https://github.com/r1chardj0n3s/parse -# -# Copyright (c) 2012-2013 Richard Jones <richard@python.org> -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# vim: set filetype=python ts=4 sw=4 et si tw=75 -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/dropbox_w_dropbox.html b/docs/_modules/py2store/persisters/dropbox_w_dropbox.html deleted file mode 100644 index 9e59967..0000000 --- a/docs/_modules/py2store/persisters/dropbox_w_dropbox.html +++ /dev/null @@ -1,362 +0,0 @@ - - - - - - - - py2store.persisters.dropbox_w_dropbox — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.dropbox_w_dropbox

-from py2store.base import Persister
-from py2store.mixins import ReadOnlyMixin
-
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-with ModuleNotFoundErrorNiceMessage():
-    from dropbox import Dropbox
-    from dropbox.files import DownloadError
-    from dropbox.files import LookupError as DropboxLookupError
-    from dropbox.exceptions import ApiError
-    from dropbox.files import WriteMode, SharedLink
-
-
-def _is_file_not_found_error(error_object):
-    if isinstance(error_object, ApiError):
-        if len(error_object.args) >= 2:
-            err = error_object.args[1]
-            if isinstance(err, DownloadError) and isinstance(
-                    err.get_path(), DropboxLookupError
-            ):
-                return True
-    return False
-
-
-
[docs]class DropboxPersister(Persister): - """ - A persister for dropbox. - You need to have the python connector (if you don't: pip install dropbox) - You also need to have a token for your dropbox app. If you don't it's a google away. - Finally, for the test below, you need to put this token in ~/.py2store_configs.json' under key - dropbox.__init__kwargs, and have a folder named /py2store_data/test/ in your app space. - - >>> import json - >>> import os - >>> - >>> configs = json.load(open(os.path.expanduser('~/.py2store_configs.json'))) - >>> s = DropboxPersister('/py2store_data/test/', **configs['dropbox']['__init__kwargs']) - >>> if '/py2store_data/test/_can_remove' in s: - ... del s['/py2store_data/test/_can_remove'] - ... - >>> - >>> n = len(s) - >>> if n == 1: - ... assert list(s) == ['/py2store_data/test/_can_remove'] - ... - >>> s['/py2store_data/test/_can_remove'] = b'this is a test' - >>> assert len(s) == n + 1 - >>> assert s['/py2store_data/test/_can_remove'] == b'this is a test' - >>> '/py2store_data/test/_can_remove' in s - True - >>> del s['/py2store_data/test/_can_remove'] - """ - - def __init__( - self, - rootdir, - oauth2_access_token, - connection_kwargs=None, - files_upload_kwargs=None, - files_list_folder_kwargs=None, - rev=None, - ): - - if connection_kwargs is None: - connection_kwargs = {} - if files_upload_kwargs is None: - files_upload_kwargs = {"mode": WriteMode.overwrite} - if files_list_folder_kwargs is None: - files_list_folder_kwargs = { - "recursive": True, - "include_non_downloadable_files": False, - } - - self._prefix = rootdir - self._con = Dropbox(oauth2_access_token, **connection_kwargs) - self._connection_kwargs = connection_kwargs - self._files_upload_kwargs = files_upload_kwargs - self._files_list_folder_kwargs = files_list_folder_kwargs - self._rev = rev - - # TODO: __len__ is taken from Persister, which iterates and counts. Not efficient. Find direct api for this! - - def __iter__(self): - r = self._con.files_list_folder(self._prefix) - yield from (x.path_display for x in r.entries) - cursor = r.cursor - if r.has_more: - r = self._con.files_list_folder_continue(cursor) - yield from (x.path_display for x in r.entries) - - def __getitem__(self, k): - try: - metadata, contents_response = self._con.files_download(k) - except ApiError as e: - if _is_file_not_found_error(e): - raise KeyError(f"Key doesn't exist: {k}") - raise - - if not contents_response.status_code: - raise ValueError( - "Response code wasn't 200 when trying to download a file (yet the file seems to exist)." - ) - - return contents_response.content - - def __setitem__(self, k, v): - return self._con.files_upload(v, k, **self._files_upload_kwargs) - - def __delitem__(self, k): - return self._con.files_delete_v2(k, self._rev)
- - -def _entry_is_dir(entry): - return not hasattr(entry, "is_downloadable") - - -def _entry_is_file(entry): - return hasattr(entry, "is_downloadable") - - -def _extend_path(path, extension): - extend_path = "/" + path + "/" + extension + "/" - extend_path.replace("//", "/") - return extend_path - - -
[docs]class DropboxLinkReaderWithToken(ReadOnlyMixin, DropboxPersister): - def __init__(self, url, oauth2_access_token): - self._con = Dropbox(oauth2_access_token) - self.url = url - self.shared_link = SharedLink(url=url) - - def _yield_from_files_list_folder(self, path, path_gen): - """ - yield paths from path_gen, which can be a files_list_folder or a files_list_folder_continue, - in a depth search manner. - """ - for x in path_gen.entries: - if _entry_is_file(x): - yield x.path_display - else: - folder_path = _extend_path(path, x.name) - yield from self._get_path_gen_from_path(path=folder_path) - - if path_gen.has_more: - yield from self._get_path_gen_from_cursor( - path_gen.cursor, path=path - ) - - def _get_path_gen_from_path(self, path): - path_gen = self._con.files_list_folder( - path=path, recursive=False, shared_link=self.shared_link - ) - yield from self._yield_from_files_list_folder(path, path_gen) - - def _get_path_gen_from_cursor(self, cursor, path): - path_gen = self._con.files_list_folder_continue(cursor) - yield from self._yield_from_files_list_folder(path, path_gen) - - def __iter__(self): - yield from self._get_path_gen_from_path(path="")
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/dropbox_w_urllib.html b/docs/_modules/py2store/persisters/dropbox_w_urllib.html deleted file mode 100644 index 6e40c2e..0000000 --- a/docs/_modules/py2store/persisters/dropbox_w_urllib.html +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - py2store.persisters.dropbox_w_urllib — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.dropbox_w_urllib

-import os
-import zipfile
-import tempfile
-import urllib.request
-from py2store.base import KvReader
-
-
-
[docs]class DropboxFolderCopyReader(KvReader): - """Makes a full local copy of the folder (by default, to a local temp folder) and gives access to it. - """ - - def __init__(self, url, path=tempfile.gettempdir()): - self.url = url - self.path = path - - os.makedirs(self.path, exist_ok=True) - self._zip_filepath = os.path.join(self.path, "shared_folder.zip") - - self._files = [] - self._get_folder() - - def __getitem__(self, rel_path): - real_path = os.path.join(self.path, rel_path) - try: - with open(real_path, "r") as f: - return f.read() - except FileNotFoundError: - raise KeyError(f"Key doesn't exist: {rel_path}") - - def __iter__(self): - yield from sorted(path for path in self._files if not path == "/") - - def __contains__(self, rel_path): - return rel_path in self._files - - def _get_folder(self): - download_from_dropbox(self.url, self._zip_filepath) - self._unzip() - os.remove(self._zip_filepath) - - def _unzip(self): - with zipfile.ZipFile(self._zip_filepath, "r") as zip_ref: - zip_ref.extractall(self.path) - self._files = zip_ref.namelist()
- - -
[docs]class DropboxFileCopyReader(KvReader): - def __init__(self, url, path=None): - self.url = url - self.path = path or self._get_filename_from_url() - - download_from_dropbox(self.url, self.path) - self.file = open(self.path, "r") - - def __getitem__(self, index): - self.file.seek(0) - return self.readlines()[index] - - def __iter__(self): - self.file.seek(0) - yield from self.file - - def __len__(self): - return len(self.readlines()) - - def __contains__(self, k): - return k in self.read() - - def __del__(self): - if self.file: - self.file.close() - self.file = None - - def read(self): - self.file.seek(0) - return self.file.read() - - def readlines(self): - self.file.seek(0) - return self.file.readlines() - - def _get_filename_from_url(self): - # 'https:...txt?dl=0&smth=else' -> 'https:...txt' - url_w_no_params = self.url.split("?", 1)[0] - - # 'https://www.dropbox.com/.../my_file.txt' -> 'my_file.txt' - last_part_of_urls_path = url_w_no_params.rsplit("/", 1)[-1] - return last_part_of_urls_path
- - -DFLT_USER_AGENT = "Wget/1.16 (linux-gnu)" - - -def download_from_dropbox( - url, file, chk_size=1024, user_agent=DFLT_USER_AGENT -): - def iter_content_and_copy_to(file): - req = urllib.request.Request(url) - req.add_header("user-agent", user_agent) - with urllib.request.urlopen(req) as response: - while True: - chk = response.read(chk_size) - if len(chk) > 0: - file.write(chk) - else: - break - - if not isinstance(file, str): - iter_content_and_copy_to(file) - else: - with open(file, "wb") as _target_file: - iter_content_and_copy_to(_target_file) - - -def bytes_from_dropbox(url, chk_size=1024, user_agent=DFLT_USER_AGENT): - from io import BytesIO - - with BytesIO() as file: - download_from_dropbox( - url, file, chk_size=chk_size, user_agent=user_agent - ) - file.seek(0) - return file.read() - -# DFLT_USER_AGENT = 'Wget/1.16 (linux-gnu)' -# def download_from_dropbox(url, file, as_zip=False, chunk_size=1024, user_agent=DFLT_USER_AGENT): -# -# response = requests.get( -# url, -# params={'dl': int(as_zip)}, -# headers={'user-agent': user_agent}, -# stream=True, -# ) -# -# def iter_content_and_copy_to(file): -# for chunk in response.iter_content(chunk_size=chunk_size): -# if chunk: -# file.write(chunk) -# -# if not isinstance(file, str): -# iter_content_and_copy_to(file) -# else: -# with open(file, 'wb') as _target_file: -# iter_content_and_copy_to(_target_file) -# -# -# def bytes_from_dropbox(url, chunk_size=1024, user_agent=DFLT_USER_AGENT): -# from io import BytesIO -# with BytesIO() as file: -# download_from_dropbox(url, file, chunk_size=chunk_size, user_agent=user_agent) -# file.seek(0) -# return file.read() -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/dynamodb_w_boto3.html b/docs/_modules/py2store/persisters/dynamodb_w_boto3.html deleted file mode 100644 index c3a02af..0000000 --- a/docs/_modules/py2store/persisters/dynamodb_w_boto3.html +++ /dev/null @@ -1,362 +0,0 @@ - - - - - - - - py2store.persisters.dynamodb_w_boto3 — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.dynamodb_w_boto3

-import boto3
-
-from py2store.base import Persister
-
-
-
[docs]class NoSuchKeyError(KeyError): - pass
- - -
[docs]class DynamoDbPersister(Persister): - """ - A basic DynamoDb via Boto3 persister. - >>> from py2store.persisters.dynamodb_w_boto3 import DynamoDbPersister - >>> s = DynamoDbPersister() - >>> k = {'key': '777'} # Each collection will happily accept user-defined _key values. - >>> v = {'val': 'bar'} - >>> for _key in s: - ... del s[_key] - ... - >>> k in s - False - >>> len(s) - 0 - >>> s[k] = v - >>> len(s) - 1 - >>> s[k] - {'val': 'bar'} - >>> s.get(k) - {'val': 'bar'} - >>> s.get({'not': 'a key'}, {'default': 'val'}) # testing s.get with default - {'default': 'val'} - >>> list(s.values()) - [{'val': 'bar'}] - >>> k in s # testing __contains__ again - True - >>> del s[k] - >>> len(s) - 0 - >>> s = DynamoDbPersister(table_name='py2store2', key_fields=('name',)) - >>> for _key in s: - ... del s[_key] - >>> len(s) - 0 - >>> s[{'name': 'guido'}] = {'yob': 1956, 'proj': 'python', 'bdfl': False} - >>> s[{'name': 'guido'}] - {'proj': 'python', 'yob': Decimal('1956'), 'bdfl': False} - >>> s[{'name': 'vitalik'}] = {'yob': 1994, 'proj': 'ethereum', 'bdfl': True} - >>> s[{'name': 'vitalik'}] - {'proj': 'ethereum', 'yob': Decimal('1994'), 'bdfl': True} - >>> for key, val in s.items(): - ... print(f"{key}: {val}") - {'name': 'vitalik'}: {'proj': 'ethereum', 'yob': Decimal('1994'), 'bdfl': True} - {'name': 'guido'}: {'proj': 'python', 'yob': Decimal('1956'), 'bdfl': False} - """ - - def __init__( - self, - aws_access_key_id="", - aws_secret_access_key="", - region_name="us-west-2", - endpoint_url="http://localhost:8000", - table_name="py2store", - key_fields=("key",), - data_fields=("data",), - ): - self._dynamodb = boto3.resource( - "dynamodb", - region_name=region_name, - endpoint_url=endpoint_url, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - ) - if isinstance(key_fields, str): - key_fields = (key_fields,) - - self._key_fields = key_fields - - if isinstance(data_fields, str): - data_fields = (data_fields,) - - self._data_fields = data_fields - - key_schema = [ - {"AttributeName": k, "KeyType": "HASH"} for k in self._key_fields - ] - attribute_definition = [ - {"AttributeName": k, "AttributeType": "S"} - for k in self._key_fields - ] - - try: - self._table = self._dynamodb.create_table( - TableName=table_name, - KeySchema=key_schema, - AttributeDefinitions=attribute_definition, - ProvisionedThroughput={ - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5, - }, - ) - # Wait until the table creation complete. - self._table.meta.client.get_waiter("table_exists").wait( - TableName="Employee" - ) - print("Table has been created, please continue to insert data.") - except Exception: - self._table = self._dynamodb.Table(table_name) - pass - - def __getitem__(self, k): - try: - if isinstance(k, str): - k = (k,) - k = {att: key for att, key in zip(self._key_fields, k)} - response = self._table.get_item(Key=k) - d = response["Item"] - return {x: d[x] for x in d if x not in self._key_fields} - except Exception as e: - raise NoSuchKeyError("Key wasn't found: {}".format(k)) - - def __setitem__(self, k, v): - if isinstance(k, str): - k = (k,) - key = {att: key for att, key in zip(self._key_fields, k)} - else: - key = k - if isinstance(v, str): - v = (v,) - val = {att: key for att, key in zip(self._data_fields, v)} - else: - val = v - - self._table.put_item(Item={**key, **val}) - - def __delitem__(self, k): - try: - if isinstance(k, str): - k = (k,) - key = {att: key for att, key in zip(self._key_fields, k)} - else: - key = k - self._table.delete_item(Key=key) - except Exception as e: - if hasattr(e, "__name__"): - if e.__name__ == "NoSuchKey": - raise NoSuchKeyError("Key wasn't found: {}".format(k)) - raise # if you got so far - - def __iter__(self): - response = self._table.scan() - yield from [ - {x: d[x] for x in d if x in self._key_fields} - for d in response["Items"] - ] - - def __len__(self): - response = self._table.scan() - return len(response["Items"])
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/ftp_persister.html b/docs/_modules/py2store/persisters/ftp_persister.html deleted file mode 100644 index 0e3c0d1..0000000 --- a/docs/_modules/py2store/persisters/ftp_persister.html +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - - - py2store.persisters.ftp_persister — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.ftp_persister

-from py2store.base import Persister
-from ftplib import FTP, all_errors
-import os.path
-from io import BytesIO
-
-
-
[docs]def remote_mkdir(ftp, remote_directory): - """ - Change to this directory, recursively making new folders if needed. - returns: True if any folders were created. - """ - if remote_directory == "/": - # absolute path so change directory to root - ftp.cwd("/") - return False - if remote_directory == "": - # top-level relative directory must exist - return False - try: - ftp.cwd(remote_directory) # sub-directory exists - except all_errors: - dirname, basename = os.path.split(remote_directory.rstrip("/")) - remote_mkdir(ftp, dirname) # make parent directories - ftp.mkd(basename) # sub-directory missing, so created it - ftp.cwd(basename) - return True - - return False
- - -
[docs]class FtpPersister(Persister): - """ - A basic ftp persister. - Keys must be names of files. - - >>> from py2store.persisters.ftp_persister import FtpPersister - >>> s = FtpPersister() - >>> k = 'foo' - >>> v = 'bar' - >>> for _key in s: - ... del s[_key] - >>> len(s) - 0 - >>> s[k] = v - >>> s[k] - 'bar' - >>> s.get(k) - 'bar' - >>> len(s) - 1 - >>> list(s.values()) - ['bar'] - >>> k in s - True - >>> del s[k] - >>> k in s - False - >>> len(s) - 0 - """ - - def __init__( - self, - user="dlpuser@dlptest.com", - password="fLDScD4Ynth0p4OJ6bW6qCxjh", - url="ftp.dlptest.com", - rootdir="./py2store", - encoding="utf8", - ): - self._ftp = FTP(host=url, user=user, passwd=password) - self._rootdir = rootdir - self._encoding = encoding - self._ftp.encoding = encoding - remote_mkdir(self._ftp, self._rootdir) - - def __getitem__(self, k): - bio = BytesIO() - self._ftp.retrbinary("RETR {}".format(k), bio.write) - bio.seek(0) - return bio.read().decode(self._encoding) - - def __setitem__(self, k, v): - bio = BytesIO(bytearray(v, encoding=self._encoding)) - self._ftp.storbinary("STOR {}".format(k), bio) - - def __delitem__(self, k): - if len(k) > 0: - try: - self._ftp.delete(k) - except all_errors: - raise KeyError(f"You can't removed that key: {k}") - else: - raise KeyError(f"You can't removed that key: {k}") - - def __contains__(self, k): - """ - Implementation of "k in self" check - """ - try: - files = self._ftp.nlst() - if k in files: - return True - except all_errors: - return False - - return False - - def __iter__(self): - yield from [f for f in self._ftp.nlst() if f != "." and f != ".."] - - def __len__(self): - files = self._ftp.nlst() - return len(files) - 2 - - def __del__(self): - """ - Close ssh session when an object is deleted - """ - self._ftp.close()
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/local_files.html b/docs/_modules/py2store/persisters/local_files.html deleted file mode 100644 index 9064651..0000000 --- a/docs/_modules/py2store/persisters/local_files.html +++ /dev/null @@ -1,668 +0,0 @@ - - - - - - - - py2store.persisters.local_files — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.local_files

-"""
-base classes to work with local files
-"""
-import os
-import re
-from glob import iglob
-from pathlib import Path
-from itertools import takewhile
-
-from dol.errors import NoSuchKeyError
-from dol.base import KeyValidationABC, KvReader
-from dol.mixins import FilteredKeysMixin, IterBasedSizedMixin
-
-from py2store.parse_format import match_re_for_fstring
-
-
-# # TODO: These imports are for back compatibility and should be removed at some point
-# from py2store.slib.zipfile import ZipReader, ZipFileReader, ZipFilesReader, FilesOfZip
-# from py2store.filesys import FileCollection, DirCollection
-
-DFLT_OPEN_MODE = ''
-
-file_sep = os.path.sep
-inf = float('infinity')
-
-
-
[docs]class FolderNotFoundError(NoSuchKeyError): - ...
- - -######################################################################################################################## -# File system navigation: Utils - - -
[docs]def ensure_slash_suffix(path: str): - r"""Add a file separation (/ or \) at the end of path str, if not already present.""" - if not path.endswith(file_sep): - return path + file_sep - else: - return path
- - -def pattern_filter(pattern): - pattern = re.compile(pattern) - - def _pattern_filter(s): - return pattern.match(s) is not None - - return _pattern_filter - - -def paths_in_dir_with_slash_suffix_for_dirs(rootdir): - for f in iglob(ensure_slash_suffix(rootdir) + '*'): - if os.path.isdir(f): - yield ensure_slash_suffix(f) - else: - yield f - - -def iter_relative_files_and_folder(root_folder): - root_folder = ensure_slash_suffix(root_folder) - return map(lambda x: x.replace(root_folder, ''), iglob(root_folder + '*')) - - -def iter_filepaths_in_folder(root_folder): - return ( - os.path.join(root_folder, name) - for name in iter_relative_files_and_folder(root_folder) - ) - - -def paths_in_dir(rootdir): - return iglob(ensure_slash_suffix(rootdir) + '*') - - -def filepaths_in_dir(rootdir): - return filter(os.path.isfile, iglob(ensure_slash_suffix(rootdir) + '*')) - - -def dirpaths_in_dir(rootdir): - return filter(os.path.isdir, iglob(ensure_slash_suffix(rootdir) + '*')) - - -def iter_filepaths_in_folder_recursively( - root_folder, max_levels=None, _current_level=0 -): - if max_levels is None: - max_levels = inf - for full_path in paths_in_dir(root_folder): - if os.path.isdir(full_path): - if _current_level < max_levels: - for entry in iter_filepaths_in_folder_recursively( - full_path, max_levels, _current_level + 1 - ): - yield entry - else: - if os.path.isfile(full_path): - yield full_path - - -def iter_dirpaths_in_folder_recursively( - root_folder, max_levels=None, _current_level=0 -): - if max_levels is None: - max_levels = inf - for full_path in paths_in_dir(root_folder): - if os.path.isdir(full_path): - yield full_path - if _current_level < max_levels: - for entry in iter_dirpaths_in_folder_recursively( - full_path, max_levels, _current_level + 1 - ): - yield entry - - -class PrefixedFilepaths: - """ - Keys collection for local files, where the keys are full filepaths DIRECTLY under a given root dir _prefix. - This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)). - """ - - _max_levels = None - - def __iter__(self): - return iter_relative_files_and_folder( - self._prefix, max_levels=self._max_levels - ) - - def __contains__(self, k): - """ - Check if filepath exists (i.e. the path exists and is a file) - :param k: A key to search for - :return: True if k exists, False if not - """ - return os.path.isfile(k) - - -class PrefixedFilepathsRecursive(PrefixedFilepaths): - """ - Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. - This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)). - """ - - _max_levels = None - - def __iter__(self): - return iter_filepaths_in_folder_recursively( - self._prefix, max_levels=self._max_levels - ) - - -class PrefixedDirpathsRecursive(PrefixedFilepaths): - """ - Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. - This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)). - """ - - _max_levels = None - - def __iter__(self): - return iter_dirpaths_in_folder_recursively( - self._prefix, max_levels=self._max_levels - ) - - -def path_match_regex_from_path_format(path_format): - if '{' not in path_format: - # if the path_format is equal to the _prefix (i.e. there's no {} formatting) - # ... append a formatting element so that the matcher can match all subfiles. - path_format = path_format + '{}' - - return match_re_for_fstring(path_format) - - -class PathFormat: - def __init__(self, path_format: str): - """ - A class for pattern-filtered exploration of file paths. - :param path_format: The f-string format that the fullpath keys of the obj source should have. - Often, just the root directory whose FILES contain the (full_filepath, content) data - Also common is to use path_format='{rootdir}/{relative_path}.EXT' to impose a specific extension EXT - """ - self._path_format = ( - path_format # not intended for use, but keeping in case, for now - ) - - if '{' not in path_format: - rootdir = ensure_slash_suffix(path_format) - else: - rootdir = ensure_slash_suffix( - os.path.dirname(re.match(r'[^{]*', path_format).group(0)) - ) - - self._prefix = rootdir - self._path_match_re = path_match_regex_from_path_format(path_format) - - def _key_filt(k): - return bool(self._path_match_re.match(k)) - - self._key_filt = _key_filt - - def is_valid_key(self, k): - return self._key_filt(k) - - -def _is_not_dir(p): - return not p.is_dir() - - -def first_non_existing_parent_dir(dirpath): - parent = '' - for parent in takewhile(_is_not_dir, Path(dirpath).parents): - pass - return str(parent) - - -######################################################################################################################## -# Local File Persistence : Classes - -from functools import wraps -from typing import Union, Callable - - -def w_helpful_folder_not_found_error( - *, - raise_error=KeyError, - extra_msg: Union[str, Callable] = '', - caught_errors=FileNotFoundError, -): - if isinstance(extra_msg, str): - extra_msg_str = extra_msg - - def extra_msg(*args, **kwargs): - return extra_msg_str - - assert callable(extra_msg), 'extra_msg must be a callable or a string' - - def _helpful_folder_not_found_error(method): - @wraps(method) - def wrapped_method(*args, **kwargs): - try: - return method(*args, **kwargs) - except caught_errors as e: - msg = '{}: {}\n'.format(type(e).__name__, e) + extra_msg( - *args, **kwargs - ) - raise raise_error(msg) - - return wrapped_method - - return _helpful_folder_not_found_error - - -def _store_does_not_create_dirs_msg(self, k, *args, **kwargs): - return ( - "The store you're using doesn't create directories for you. " - 'You have to make the directories needed yourself manually, ' - 'or use a store that does that for you (example QuickStore). ' - "This is the first directory that didn't exist:\n" - f'{first_non_existing_parent_dir(k)}' - ) - - -
[docs]class LocalFileStreamGetter: - """A class to get stream objects of local open files. - The class can only get keys, and only to read, write (destructive or append). - - >>> from tempfile import mkdtemp - >>> import os - >>> rootdir = mkdtemp() - >>> - >>> appendable_stream = LocalFileStreamGetter(mode='a+') - >>> reader = PathFormatPersister(rootdir) - >>> filepath = os.path.join(rootdir, 'tmp.txt') - >>> - >>> with appendable_stream[filepath] as fp: - ... fp.write('hello') - 5 - >>> print(reader[filepath]) - hello - >>> with appendable_stream[filepath] as fp: - ... fp.write(' world') - 6 - >>> - >>> print(reader[filepath]) - hello world - """ - - def __init__(self, **open_kwargs): - self.open_kwargs = open_kwargs - - @w_helpful_folder_not_found_error() - def __getitem__(self, k): - return open(k, **self.open_kwargs)
- - -# TODO: Use LocalFileStream -
[docs]class LocalFileRWD: - """ - A class providing get, set and delete functionality using local files as the storage backend. - """ - - def __init__(self, mode='', **open_kwargs): - assert mode in {'', 'b', 't'}, "mode should be '', 'b', or 't'" - - read_mode = open_kwargs.pop('read_mode', 'r' + mode) - write_mode = open_kwargs.pop('write_mode', 'w' + mode) - self._open_kwargs_for_read = dict(open_kwargs, mode=read_mode) - self._open_kwargs_for_write = dict(open_kwargs, mode=write_mode) - - @w_helpful_folder_not_found_error() - def __getitem__(self, k): - with open(k, **self._open_kwargs_for_read) as fp: - data = fp.read() - return data - - @w_helpful_folder_not_found_error( - raise_error=FolderNotFoundError, - extra_msg=_store_does_not_create_dirs_msg, - ) - def __setitem__(self, k, v): - with open(k, **self._open_kwargs_for_write) as fp: - fp.write(v) - - @w_helpful_folder_not_found_error() - def __delitem__(self, k): - return os.remove(k)
- - -
[docs]class FilepathFormatKeys( - PathFormat, - FilteredKeysMixin, - KeyValidationABC, - PrefixedFilepathsRecursive, - IterBasedSizedMixin, -): - def __init__(self, path_format: str, max_levels: int = inf): - super().__init__(path_format) - self._max_levels = max_levels
- - -
[docs]class DirpathFormatKeys( - PathFormat, - FilteredKeysMixin, - KeyValidationABC, - PrefixedDirpathsRecursive, - IterBasedSizedMixin, -): - def __init__(self, path_format: str, max_levels: int = inf): - super().__init__(path_format) - self._max_levels = max_levels
- - -
[docs]class PathFormatPersister(FilepathFormatKeys, LocalFileRWD): - def __init__( - self, - path_format, - max_levels: int = inf, - mode=DFLT_OPEN_MODE, - **open_kwargs, - ): - FilepathFormatKeys.__init__(self, path_format) - LocalFileRWD.__init__(self, mode, **open_kwargs) - self._max_levels = max_levels
- - -
[docs]class PrefixedFilepaths: - """ - Keys collection for local files, where the keys are full filepaths DIRECTLY under a given root dir _prefix. - This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)). - """ - - def __iter__(self): - return iter_relative_files_and_folder(self._prefix) - - def __contains__(self, k): - """ - Check if filepath exists (i.e. the path exists and is a file) - :param k: A key to search for - :return: True if k exists, False if not - """ - return os.path.isfile(k)
- - -
[docs]class PrefixedFilepathsRecursive(PrefixedFilepaths): - """ - Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. - This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)). - """ - - def __iter__(self): - return iter_filepaths_in_folder_recursively(self._prefix)
- - -
[docs]class PrefixedDirpathsRecursive(PrefixedFilepaths): - """ - Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. - This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)). - """ - - def __iter__(self): - return iter_dirpaths_in_folder_recursively(self._prefix)
- - -is_dir_path = os.path.isdir -is_file_path = os.path.isfile - - -def extend_prefix(prefix, new_prefix): - return ensure_slash_suffix(os.path.join(prefix, new_prefix)) - - -def endswith_slash(path): - return path.endswith(file_sep) - - -
[docs]class FileReader(KvReader): - """ KV Reader whose keys are paths and values are: - - Another FileReader if a path points to a directory - - The bytes of the file if the path points to a file. - """ - - def __init__(self, rootdir): - self.rootdir = ensure_slash_suffix(rootdir) - self._rootdir_length = len(self.rootdir) - # TODO: Look into alternatives for the raison d'etre of _new_node and _class_name - # (They are there, because using self.__class__ directly goes to super) - self._new_node = type(self) - self._class_name = type(self).__name__ - - def _extended_prefix(self, new_prefix): - return os.path.join(self.rootdir, new_prefix) - - # TODO: Possible optimization: Think if using cached keys makes more sense. - def __contains__(self, k): - return ( - k.startswith(self.rootdir) # prefix is rootdir - and os.path.exists(k) # exists (as file or dir) - and ( - k.endswith(file_sep) - or file_sep not in k[self._rootdir_length :] - ) # is a dir or a first-level file - ) - - def __iter__(self): - for path in map( - self._extended_prefix, # (2) extend prefix with sub-path name - os.listdir(self.rootdir), # (1) list file names under _prefix - ): - if is_dir_path(path): - yield ensure_slash_suffix(path) - else: - yield path - - def __getitem__(self, k): - if k in self: - if is_dir_path(k): - return self._new_node(k) - elif is_file_path(k): - with open(k, 'rb') as fp: - return fp.read() - return self.__missing__( - k - ) # if you got this far, the key is missing (or malformed) - - def __missing__(self, k): - raise NoSuchKeyError( - f"No such key (perhaps it's not a valid path, or was deleted?): {k}" - ) - - def __repr__(self): - return f"{self._class_name}('{self.rootdir}')"
- - -
[docs]class DirReader(FileReader): - """ KV Reader whose keys (AND VALUES) are directory full paths of the subdirectories of rootdir. - """ - - def _extended_prefix(self, new_prefix): - return ensure_slash_suffix(super()._extended_prefix(new_prefix)) - - def __contains__(self, k): - return endswith_slash(k) and self.__contains__(k) - - def __iter__(self): - return filter(endswith_slash, super().__iter__())
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/mongo_w_pymongo.html b/docs/_modules/py2store/persisters/mongo_w_pymongo.html deleted file mode 100644 index 096d705..0000000 --- a/docs/_modules/py2store/persisters/mongo_w_pymongo.html +++ /dev/null @@ -1,508 +0,0 @@ - - - - - - - - py2store.persisters.mongo_w_pymongo — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.mongo_w_pymongo

-from py2store.base import KvReader, Persister
-from py2store.util import ModuleNotFoundErrorNiceMessage
-from functools import wraps
-
-with ModuleNotFoundErrorNiceMessage():
-    from pymongo import MongoClient
-
-
-
[docs]class MongoCollectionReader(KvReader): - """ - - """ - - def __init__(self, mgc=None, key_fields=("_id",), data_fields=None): - if mgc is None: - mgc = _mk_dflt_mgc() - self._mgc = mgc - if isinstance(key_fields, str): - key_fields = (key_fields,) - if data_fields is None: - pass - - self._key_projection = {k: True for k in key_fields} - if "_id" not in key_fields: - self._key_projection.update( - _id=False - ) # need to explicitly specify this since mongo includes _id by dflt - if data_fields is None: - data_fields = {k: False for k in key_fields} - elif not isinstance(data_fields, dict): - data_fields = {k: True for k in data_fields} - if "_id" not in data_fields: - data_fields["_id"] = False - self._data_fields = data_fields - self._key_fields = key_fields - - @classmethod - def from_params( - cls, - db_name="py2store", - collection_name="test", - key_fields=("_id",), - data_fields=None, - mongo_client=None, - ): - if mongo_client is None: - mongo_client = MongoClient() - elif isinstance(mongo_client, dict): - mongo_client = MongoClient(**mongo_client) - - return cls( - mgc=mongo_client[db_name][collection_name], - key_fields=key_fields, - data_fields=data_fields, - ) - - def __getitem__(self, k): - doc = self._mgc.find_one(k, projection=self._data_fields) - if doc is not None: - return doc - else: - raise KeyError(f"No document found for query: {k}") - - def __iter__(self): - yield from self._mgc.find(projection=self._key_projection) - - def __len__(self): - return self._mgc.count_documents({}) - - def __length_hint__(self): - return self._mgc.estimated_document_count()
- - -
[docs]class MongoCollectionPersister(MongoCollectionReader): - """ - >>> s = MongoPersister() # just use defaults - >>> for _id in s: # deleting all docs in tmp - ... del s[_id] - >>> k = {'_id': 'foo'} - >>> v = {'val': 'bar'} - >>> k in s # see that key is not in store (and testing __contains__) - False - >>> len(s) - 0 - >>> s[k] = v - >>> len(s) - 1 - >>> list(s) - [{'_id': 'foo'}] - >>> s[k] - {'val': 'bar'} - >>> s.get(k) - {'val': 'bar'} - >>> s.get({'not': 'a key'}, {'default': 'val'}) # testing s.get with default - {'default': 'val'} - >>> list(s.values()) - [{'val': 'bar'}] - >>> k in s # testing __contains__ again - True - >>> del s[k] - >>> len(s) - 0 - >>> - >>> # Making a persister whose keys are 2-dimensional and values are 3-dimensional - >>> s = MongoPersister(db_name='py2store', collection_name='tmp', - ... key_fields=('first', 'last'), data_fields=('yob', 'proj', 'bdfl')) - >>> for _id in s: # deleting all docs in tmp - ... del s[_id] - >>> # writing two items - >>> s[{'first': 'Guido', 'last': 'van Rossum'}] = {'yob': 1956, 'proj': 'python', 'bdfl': False} - >>> s[{'first': 'Vitalik', 'last': 'Buterin'}] = {'yob': 1994, 'proj': 'ethereum', 'bdfl': True} - >>> # Seeing that those two items are there - >>> for key, val in s.items(): - ... print(f"{key} --> {val}") - {'first': 'Guido', 'last': 'van Rossum'} --> {'yob': 1956, 'proj': 'python', 'bdfl': False} - {'first': 'Vitalik', 'last': 'Buterin'} --> {'yob': 1994, 'proj': 'ethereum', 'bdfl': True} - """ - - def __setitem__(self, k, v): - return self._mgc.insert_one(dict(k, **v)) - - def __delitem__(self, k): - if len(k) > 0: - return self._mgc.delete_one(k) - else: - raise KeyError(f"You can't removed that key: {k}")
- - -
[docs]class MongoAppendablePersister(MongoCollectionPersister): - def append(self, v): - return self._mgc.insert_one(v) - - def extend(self, items): - return self._mgc.insert_many(items)
- - -
[docs]class MongoClientReader(KvReader): - @wraps(MongoClient.__init__) - def __init__(self, **mongo_client_kwargs): - self._mongo_client = MongoClient(**mongo_client_kwargs) - - def __iter__(self): - yield from self._mongo_client.list_database_names() - - def __getitem__(self, k): - return MongoDbReader( - db_name=k, mongo_client=self._mongo_client - ) # or just wrap self._mongo_client[k]?
- - -
[docs]class MongoDbReader(KvReader): - def __init__( - self, - db_name="py2store", - collection_store_cls=MongoCollectionReader, - mongo_client=None, - **mongo_client_kwargs, - ): - if mongo_client is None: - self._mongo_client = MongoClient(**mongo_client_kwargs) - elif isinstance(mongo_client, dict): - self._mongo_client = MongoClient(**mongo_client) - else: - self._mongo_client = mongo_client - self._db_name = db_name - self.db = self._mongo_client[db_name] - self.collection_store_cls = collection_store_cls - - def __iter__(self): - yield from self.db.list_collection_names() - - def __getitem__(self, k): - return self.collection_store_cls(self.db[k])
- - -def _mk_dflt_mgc(): - return MongoClient()["py2store"]["test"] - - -
[docs]class OldMongoPersister(Persister): - """ - A basic mongo persister. - Note that the mongo persister is designed not to overwrite the value of a key if the key already exists. - You can subclass it and use update_one instead of insert_one if you want to be able to overwrite data. - - >>> s = OldMongoPersister() # just use defaults - >>> for _id in s: # deleting all docs in tmp - ... del s[_id] - >>> k = {'_id': 'foo'} - >>> v = {'val': 'bar'} - >>> k in s # see that key is not in store (and testing __contains__) - False - >>> len(s) - 0 - >>> s[k] = v - >>> len(s) - 1 - >>> list(s) - [{'_id': 'foo'}] - >>> s[k] - {'val': 'bar'} - >>> s.get(k) - {'val': 'bar'} - >>> s.get({'not': 'a key'}, {'default': 'val'}) # testing s.get with default - {'default': 'val'} - >>> list(s.values()) - [{'val': 'bar'}] - >>> k in s # testing __contains__ again - True - >>> del s[k] - >>> len(s) - 0 - >>> - >>> # Making a persister whose keys are 2-dimensional and values are 3-dimensional - >>> s = OldMongoPersister(db_name='py2store', collection_name='tmp', - ... key_fields=('first', 'last'), data_fields=('yob', 'proj', 'bdfl')) - >>> for _id in s: # deleting all docs in tmp - ... del s[_id] - >>> # writing two items - >>> s[{'first': 'Guido', 'last': 'van Rossum'}] = {'yob': 1956, 'proj': 'python', 'bdfl': False} - >>> s[{'first': 'Vitalik', 'last': 'Buterin'}] = {'yob': 1994, 'proj': 'ethereum', 'bdfl': True} - >>> # Seeing that those two items are there - >>> for key, val in s.items(): - ... print(f"{key} --> {val}") - {'first': 'Guido', 'last': 'van Rossum'} --> {'yob': 1956, 'proj': 'python', 'bdfl': False} - {'first': 'Vitalik', 'last': 'Buterin'} --> {'yob': 1994, 'proj': 'ethereum', 'bdfl': True} - """ - - def __init__( - self, - db_name="py2store", - collection_name="test", - key_fields=("_id",), - data_fields=None, - mongo_client_kwargs=None, - ): - if mongo_client_kwargs is None: - mongo_client_kwargs = {} - self._mongo_client = MongoClient(**mongo_client_kwargs) - self._db_name = db_name - self._collection_name = collection_name - self._mgc = self._mongo_client[db_name][collection_name] - if isinstance(key_fields, str): - key_fields = (key_fields,) - if data_fields is None: - pass - - self._key_projection = {k: True for k in key_fields} - if "_id" not in key_fields: - self._key_projection.update( - _id=False - ) # need to explicitly specify this since mongo includes _id by dflt - if data_fields is None: - data_fields = {k: False for k in key_fields} - elif not isinstance(data_fields, dict): - data_fields = {k: True for k in data_fields} - if "_id" not in data_fields: - data_fields["_id"] = False - self._data_fields = data_fields - self._key_fields = key_fields - - def __getitem__(self, k): - doc = self._mgc.find_one(k, projection=self._data_fields) - if doc is not None: - return doc - else: - raise KeyError(f"No document found for query: {k}") - - def __setitem__(self, k, v): - return self._mgc.insert_one(dict(k, **v)) - - def __delitem__(self, k): - if len(k) > 0: - return self._mgc.delete_one(k) - else: - raise KeyError(f"You can't removed that key: {k}") - - def __iter__(self): - yield from self._mgc.find(projection=self._key_projection) - - def __len__(self): - return self._mgc.count_documents({})
- - -
[docs]class OldMongoInsertPersister(OldMongoPersister): - def __init__( - self, - db_name="py2store", - collection_name="test", - data_fields=None, - mongo_client_kwargs=None, - ): - super().__init__( - db_name=db_name, - collection_name=collection_name, - data_fields=data_fields, - key_fields=("_id",), - mongo_client_kwargs=mongo_client_kwargs, - ) - - def append(self, v): - return self._mgc.insert_one(v) - - def extend(self, items): - return self._mgc.insert_many(items)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/new_s3.html b/docs/_modules/py2store/persisters/new_s3.html deleted file mode 100644 index f9b6ab8..0000000 --- a/docs/_modules/py2store/persisters/new_s3.html +++ /dev/null @@ -1,726 +0,0 @@ - - - - - - - - py2store.persisters.new_s3 — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.new_s3

-from functools import partial, wraps
-from typing import Iterable, NewType, Union, Callable, Any
-from collections.abc import Mapping
-from dataclasses import dataclass
-from warnings import warn
-
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-with ModuleNotFoundErrorNiceMessage():
-    from botocore.client import Config, BaseClient
-    from botocore.exceptions import ClientError
-    from botocore.response import StreamingBody
-    from boto3 import client
-    from boto3.resources.base import ServiceResource
-
-    S3BucketType = NewType(
-        "S3BucketType", ServiceResource
-    )  # TODO: hack -- find how to import an actual Bucket type
-
-from py2store.base import KvReader, KvPersister, Collection
-
-
-
[docs]class S3KeyError(KeyError): - ...
- - -
[docs]class NoSuchKeyError(S3KeyError): - ...
- - -
[docs]class KeyNotValidError(S3KeyError): - ...
- - -
[docs]class GetItemForKeyError(S3KeyError): - ...
- - -
[docs]class S3HttpError(RuntimeError): - ...
- - -
[docs]class S3Not200StatusCodeError(S3HttpError): - ...
- - -def raise_on_error(d: dict): - raise - - -def return_none_on_error(d: dict): - return None - - -def return_empty_tuple_on_error(d: dict): - return () - - -OnErrorType = Union[Callable[[dict], Any], str] - - -def path_get( - mapping, - path, - on_error: OnErrorType = raise_on_error, - caught_errors=(KeyError,), -): - result = mapping - for k in path: - try: - result = result[k] - except caught_errors as error: - if callable(on_error): - return on_error( - dict( - mapping=mapping, - path=path, - result=result, - k=k, - error=error, - ) - ) - elif isinstance(on_error, str): - try: - raise error.__class__( - on_error - ) # use on_error as a message, raising the same error class - except Exception: - raise S3KeyError( - on_error - ) # if that doesn't work, just raise a S3KeyError - else: - raise ValueError( - f"on_error should be a callable (input is a dict) or a string. " - f"Was: {on_error}" - ) - return result - - -encode_as_utf8 = partial(str, encoding="utf-8") - -# TODO: Make capability of overriding defaults externally. -DFLT_S3_OBJ_OF_DATA = encode_as_utf8 -DFLT_AWS_S3_ENDPOINT = "https://s3.amazonaws.com" -DFLT_BOTO_CLIENT_VERIFY = None -DFLT_SIGNATURE_VERSION = "s3v4" -DFLT_CONFIG = Config(signature_version=DFLT_SIGNATURE_VERSION) - - -def get_s3_client( - aws_access_key_id, - aws_secret_access_key, - endpoint_url=DFLT_AWS_S3_ENDPOINT, - verify=DFLT_BOTO_CLIENT_VERIFY, - config=DFLT_CONFIG, -): - return client( - "s3", - endpoint_url=endpoint_url, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - verify=verify, - config=config, - ) - - -
[docs]def ensure_client(candidate_client): - """Ensure input is a BaseClient (either kwargs to make one, or already a BaseClient instance.""" - if isinstance(candidate_client, Mapping): - return get_s3_client( - **candidate_client - ) # consider candidate_client as kwargs to make one - # TODO: Be more precise (botocore.client.S3 doesn't exist, so took BaseClient): - assert isinstance(candidate_client, BaseClient) - return candidate_client
- - -def isdir_key(key: str): - return key.endswith("/") - - -def isfile_key(key: str): - return not key.endswith("/") - - -# TODO: Consider pros/cons of subclassing or delegating dict. -# TODO: Consider reducing visual noise with staticmethod|partial composition -# Pattern: Named nested access (glom etc.) -class Resp: - @staticmethod - def status_code(d, on_error: OnErrorType = raise_on_error): - return path_get(d, ["ResponseMetadata", "HTTPStatusCode"], on_error) - - @staticmethod - def contents(d, on_error: OnErrorType = return_empty_tuple_on_error): - return path_get(d, ["Contents"], on_error) - - @staticmethod - def key(d, on_error: OnErrorType = raise_on_error): - return path_get(d, ["Key"], on_error) - - @staticmethod - def common_prefixes( - d, on_error: OnErrorType = return_empty_tuple_on_error - ): - return path_get(d, ["CommonPrefixes"], on_error) - - @staticmethod - def prefix(d, on_error: OnErrorType = raise_on_error): - return path_get(d, ["Prefix"], on_error) - - @staticmethod - def buckets(d, on_error: OnErrorType = return_empty_tuple_on_error): - return path_get(d, ["Buckets"], on_error) - - @staticmethod - def body(d, on_error: OnErrorType = return_none_on_error): - return path_get(d, ["Body"], on_error) - - @staticmethod - def ascertain_status_code( - d, - status_code=200, - raise_error=S3HttpError, - *error_args, - **error_kwargs, - ): - if Resp.status_code(d) != status_code: - raise raise_error(*error_args, **error_kwargs) - - @staticmethod - def ascertain_200_status_code(d): - status_code = Resp.status_code(d, return_none_on_error) - if status_code != 200: - if not isinstance(d, dict): - raise S3Not200StatusCodeError( - "Yeah, that's not even a dict, so doubt it's even a response." - f"I'm expecting a response over here. Instead I got a {type(d)}" - ) - elif "ResponseMetadata" in d: - raise S3Not200StatusCodeError( - f"Status code was not 200. Was {d['ResponseMetadata']}. " - f"ResponseMetadata is {d['ResponseMetadata']}" - ) - else: - raise S3Not200StatusCodeError( - f"Status code was not 200. In fact, the response dict didn't even have a ResponseMetadata key" - ) - - -
[docs]@dataclass -class S3BucketBaseReader(KvReader): - """Base bucket reader. Keys are strings, values are http responses (dicts). - - To get the actual contents from the response `v` you can do `v['Body'].read()`, or more sophisticated-ly: - ``` - if v['ResponseMetadata']['HTTPStatusCode'] == 200: - return v['Body'].read() - else: - raise RuntimeError(f"HttpError (code {v['ResponseMetadata']['HTTPStatusCode']})") - ``` - - But know that `body = v['Body']` is a `botocore.response.StreamingBody` instance, and as such, you have not - only the `body.read(amt=None)` but also - - `body.iter_chunks(chunk_size=1024)` that may be useful for large binary data, or - - `body.iter_lines(chunk_size=1024)` that may be useful for large text data. - - S3BucketBaseReader is really meant to be wrapped and/or subclassed into interfaces that do that for you. - - Example use: - ``` - client_kwargs = get_configs() # get (at least) aws_access_key_id and aws_secret_access_key - r = S3BucketBaseReader(S3BucketBaseReader.mk_client(**resources_kwargs), bucket='bucket_name', prefix='my_stuff/') - list(r) # will list file and folder names - r['my_stuff/music/'] # will give you another S3BucketBaseReader for that "subfolder" - r['my_stuff/music/with_meaning.mp3'] # will give you the response object for the contents of the file - ``` - """ - - client: BaseClient - bucket: str - prefix: str = "" - with_files: bool = True - with_directories: bool = True - - def file_obj_for_key(self, k) -> dict: - try: - return self._source.get_object(Bucket=self.bucket, Key=k) - except Exception as e: - raise GetItemForKeyError(f"Problem retrieving value for key: {k}") - - def dir_obj_for_key(self, k) -> KvReader: - # print(f"{id(self)}") - # if hasattr(self.__class__, '_cls_trans'): - # cls = type(self.__class__.__name__, (), {}) - # self.__class__._cls_trans(cls) - # else: - # cls = self.__class__ - cls = self.__class__ - return cls( - client=self._source, - bucket=self.bucket, - prefix=k, - with_files=self.with_files, - with_directories=self.with_directories, - ) - - def __post_init__(self): - if self.prefix.endswith("*"): - # msg = f"Ending with a * is a special and untested case. If you know what you're doing, go ahead though!" - self.prefix = self.prefix[:-1] # remove the * - if self.prefix != '' and not self.prefix.endswith("/"): # add the / if it's not there - self.prefix += "/" - _filt = dict(Prefix=self.prefix) # without the Delimiter='/' - if self.with_directories: - self.with_directories = False - else: - if self.prefix != '' and not self.prefix.endswith("/"): - self.prefix += "/" - _filt = dict(Prefix=self.prefix, Delimiter="/") - self.client = ensure_client(self.client) - self._source = self.client - self._filt = _filt - self._prefix = ( - self.prefix - ) # legacy: Some wrappers expect _prefix name. - - def is_valid_key(self, k) -> bool: - return isinstance(k, str) and k.startswith(self.prefix) - - def validate_key(self, k): - if not self.is_valid_key(k): - if not isinstance(k, str): - raise KeyNotValidError( - f"Key should be a string. Your key is: {k}" - ) - elif not k.startswith(self.prefix): - raise KeyNotValidError( - f"Prefix of key should be '{self.prefix}'. Your key is: {k}" - ) - else: - raise KeyNotValidError(f"Not a valid key: {k}") - - def __getitem__(self, k: str) -> Union[dict, KvReader]: - self.validate_key(k) - if isfile_key(k): - return self.file_obj_for_key(k) - else: # assume it's a "directory" - return self.dir_obj_for_key(k) - - def object_list_pages(self) -> Iterable[dict]: - yield from self._source.get_paginator("list_objects").paginate( - Bucket=self.bucket, **self._filt - ) - - def __iter__(self) -> Iterable[str]: - for resp in self.object_list_pages(): - Resp.ascertain_200_status_code(resp) - if self.with_files: - yield from filter( - lambda k: not k.endswith("/"), - map(Resp.key, Resp.contents(resp)), - ) - # for d in Resp.contents(resp): - # yield d['Key'] - if self.with_directories: - yield from map(Resp.prefix, Resp.common_prefixes(resp)) - # for d in Resp.common_prefixes(resp): - # yield d['Prefix'] - - def head_object(self, k) -> dict: - self.validate_key(k) - return self._source.head_object(Bucket=self.bucket, Key=k) - - def __contains__(self, k) -> bool: - try: - self.head_object(k) - return True # if all went well - except KeyNotValidError as e: - raise - except ClientError as e: - if e.response["Error"]["Code"] == "404": - # The object does not exist. - return False - else: - # Something else has gone wrong. - raise - - @wraps(get_s3_client) - @staticmethod - def mk_client(**client_kwargs): - return get_s3_client(**client_kwargs)
- - -
[docs]class S3BucketBasePersister(S3BucketBaseReader, KvPersister): - def __setitem__(self, k, v) -> dict: - self.validate_key(k) - return self._source.put_object(Bucket=self.bucket, Key=k, Body=v) - - def __delitem__(self, k) -> dict: - self.validate_key(k) - return self._source.delete_object(Bucket=self.bucket, Key=k)
- # TODO: Figure out how to detect if the key existed or not from the return value, so one can use this - # to align with the common behavior of `del s[k]` when `k` doesn't exist. - # Would like to avoid having to do an additional request to do `if k in s: ...` - - -# Note: The classes below don't use the usual wrappers because these don't -# handle the recursivity of S3BucketBaseReader -# TODO: Make wrappers that can follow recursive calls -# (self.__class__ should be wrapped) if the self = cls(...) and cls is wrapped!) -
[docs]class S3BucketReader(S3BucketBaseReader): - def __getitem__(self, k: str) -> Union[dict, KvReader]: - return super().__getitem__(self.prefix + k) - - def __iter__(self) -> Iterable[str]: - n = len(self.prefix) - yield from (k[n:] for k in super().__iter__())
- - -
[docs]class S3BucketPersister(S3BucketBasePersister): - def __setitem__(self, k, v): - super().__setitem__(self.prefix + k, v) - - def __delitem__(self, k): - super().__delitem__(self.prefix + k)
- - -
[docs]@dataclass -class S3Collection(Collection): - client: BaseClient - - def __post_init__(self): - self.client = ensure_client(self.client) - assert isinstance(self.client, BaseClient) - self._source = self.client - - def __iter__(self) -> Iterable[str]: - resp = self._source.list_buckets() - Resp.ascertain_200_status_code(resp) - yield from (bucket["Name"] for bucket in Resp.buckets(resp)) - - @wraps(get_s3_client) - @staticmethod - def mk_client(**client_kwargs): - return get_s3_client(**client_kwargs)
- - -# TODO: Make some stores that have more convenient interfaces -# (e.g. see existing py2store.store.s3_store and rewrite versions of those stores that use the -# present new way) - - -def get_bucket_reader(s3_collection, bucket): - return S3BucketBaseReader(s3_collection._source, bucket) - - -# Pattern: reader from collection + mapping maker. -# TODO: Make a special factory out of the pattern -
[docs]class S3BaseReader(S3Collection, KvReader): - item_getter = get_bucket_reader - - def __getitem__(self, k): - return self.item_getter(k)
- - -import pickle -from py2store.base import Store - -# from py2store.persisters.s3_w_boto3 import S3BucketPersister -from py2store.key_mappers.paths import mk_relative_path_store - -from py2store.trans import wrap_kvs - - -def _get_body_when_file(k, v): - if isfile_key(k): - Resp.ascertain_200_status_code(v) - return Resp.body(v, on_error=f"Couldn't get the body from: {v}") - else: - return v - - -def _read_body_when_file(k, v): - if isfile_key(k): - return v.read() - else: - return v - - -def _bytes_when_file(k, v): - v = _get_body_when_file(k, v) - v = _read_body_when_file(k, v) - return v - - -S3AbsPathBodyStore = wrap_kvs( - S3BucketBasePersister, - name="S3AbsPathBodyStore", - postget=_get_body_when_file, -) - -S3AbsPathBinaryStore = wrap_kvs( - S3AbsPathBodyStore, - name="S3AbsPathBinaryStore", - postget=_read_body_when_file, -) - -## Note: Alternative definition, using subclassing and checking value to determine if file (body) or not -# class S3AbsPathBinaryStore(S3AbsPathBodyStore): -# def _obj_of_data(self, data): -# body_or_dirobj = super()._obj_of_data(data) -# if isinstance(body_or_dirobj, StreamingBody): -# return body_or_dirobj.read() # Note: This part has other options (like iter_chunks(), etc.) -# else: -# return body_or_dirobj - - -# S3BinaryStore = mk_relative_path_store( -# S3AbsPathBinaryStore, __name__="S3BinaryStore", prefix_attr="prefix" -# ) - -S3BinaryReader = wrap_kvs( - S3BucketReader, - name="S3BinaryReader", - postget=_bytes_when_file, -) - -S3BinaryStore = wrap_kvs( - S3BucketPersister, - name="S3BinaryStore", - postget=_bytes_when_file, -) - - -def asis_if_not_bytes(method): - @wraps(method) - def wrapped_method(self, data): - if isinstance(data, bytes): - return method(self, data) - else: - return data - - return wrapped_method - - -
[docs]class S3TextStore(S3BinaryStore): - def _obj_of_data(self, data): - if isinstance(data, bytes): - return data.decode() - else: - return data
- - -S3StringStore = S3TextStore - - -
[docs]class S3PickleStore(S3BinaryStore): - def _obj_of_data(self, data): - return pickle.loads(data) - - def _data_of_obj(self, obj): - return pickle.dumps(obj)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/redis_w_redis.html b/docs/_modules/py2store/persisters/redis_w_redis.html deleted file mode 100644 index 4acc0f8..0000000 --- a/docs/_modules/py2store/persisters/redis_w_redis.html +++ /dev/null @@ -1,453 +0,0 @@ - - - - - - - - py2store.persisters.redis_w_redis — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.redis_w_redis

-"""
-
-Needs python's redis package:
-```
-    pip install redis
-```
-Needs a redis server running.
-
-To get/install Redis: https://redis.io/topics/quickstart
-
-To launch local redis service.
-```
-    redis-server
-```
-"""
-from py2store.base import Collection, KvReader, KvPersister
-from py2store.util import ModuleNotFoundErrorNiceMessage
-from functools import wraps
-
-
-class RedisType:
-    string = b"string"
-    list = b"list"
-
-
-with ModuleNotFoundErrorNiceMessage():
-    from redis import Redis
-
-
-class RedisFactories:
-    @classmethod
-    def from_source(cls, source):
-        """Makes an instance initialized with attribute '_source' from given source"""
-        self = object.__new__(cls)
-        self._source = source
-        return self
-
-    @classmethod
-    def from_sourced_object(cls, obj):
-        """Makes a instance initialized with '_source' taken from obj._source (existence assumed)"""
-        return cls.from_source(obj._source)
-
-
-# TODO: Check:
-#   Redis object seems to have getitem, setitem and delitem, so might already have Mapping API
-
-
-
[docs]class RedisBytesCollection(Collection, RedisFactories): - @wraps(Redis.__init__) - def __init__(self, **kwargs): - self._source = Redis(**kwargs) - - def __iter__(self): - yield from self._source.keys() - - def __getitem__(self, k): - return self._source.get(k) - - def __contains__(self, k): - return k in self._source - - def __len__(self): - return len(self._source.keys()) # TODO: Any source method for this?
- - -
[docs]class RedisBytesReader(RedisBytesCollection, KvReader): - def __getitem__(self, k): - return self._source.get(k)
- - -
[docs]class RedisBytesPersister(RedisBytesReader, KvPersister): - """ - Provides a `collections.abc.MutableMapping` (i.e. dict-like) interface to Redis. - - Note that Redis automatically converts everything to bytes when writing, which means that - read and write are not inverse of each other in the base RedisPersister. - A serialization/deserialization layer can be added to make read and write consistent. - - >>> s = RedisBytesPersister() # plenty of params possible (all those of redis.Redis), but taking defaults. - >>> - >>> # clear the kehys we'll be using - >>> keys = ['_pyst_test_str', '_pyst_test_int', '_pyst_test_float'] - >>> for k in keys: - ... del s[k] - >>> - >>> before_length = len(s) - >>> - >>> s['_pyst_test_str'] = 'hello' - >>> s['_pyst_test_str'] # note you won't be getting a str but bytes - b'hello' - >>> - >>> '_pyst_test_str' in s - >>> - >>> # numbers are converted to strings then bytes - >>> s['_pyst_test_int'] = 42 - >>> assert s['_pyst_test_int'] == b'42' - >>> s['_pyst_test_float'] = 3.14 - >>> assert s['_pyst_test_float'] == b'3.14' - >>> - >>> assert len(s) == before_length + 3 - >>> - >>> '_pyst_test_float' in - >>> - >>> # clean up - >>> for k in keys: - ... del s[k] - >>> - """ - - write_kwargs = dict(ex=None, px=None, nx=False, xx=False, keepttl=False) - - def __setitem__(self, k, v): - # TODO: Extend to a store that can handle some basic python types (lists, ints, floats) - return self._source.set(k, v, **self.write_kwargs) - - def __delitem__(self, k): - return self._source.__delitem__(k)
- - -# TODO: Make py2store types to handle Redis value types in a consistent matter -"""Type Commands -Sets SADD, SCARD, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SISMEMBER, SMEMBERS, SMOVE, - SPOP, SRANDMEMBER, SREM, SSCAN, SUNION, SUNIONSTORE -Hashes HDEL, HEXISTS, HGET, HGETALL, HINCRBY, HINCRBYFLOAT, HKEYS, HLEN, HMGET, - HMSET, HSCAN, HSET, HSETNX, HSTRLEN, HVALS -Lists BLPOP, BRPOP, BRPOPLPUSH, LINDEX, LINSERT, LLEN, LPOP, LPUSH, LPUSHX, LRANGE, - LREM, LSET, LTRIM, RPOP, RPOPLPUSH, RPUSH, RPUSHX -Strings APPEND, BITCOUNT, BITFIELD, BITOP, BITPOS, DECR, DECRBY, GET, GETBIT, GETRANGE, GETSET, - INCR, INCRBY, INCRBYFLOAT, MGET, MSET, MSETNX, PSETEX, SET, SETBIT, SETEX, SETNX, - SETRANGE, STRLEN -""" - -"""See 729 - py2store - Redis note book to see how to generate the following: -lset: (self, name, index, value) - Set ``position`` of list ``name`` to ``value``... -lrange: (self, name, start, end) - Return a slice of the list ``name`` between... -lpush: (self, name, *values) - Push ``values`` onto the head of the list ``name``... -lrem: (self, name, count, value) - Remove the first ``count`` occurrences of elements equal to ``value``... -ltrim: (self, name, start, end) - Trim the list ``name``, removing all values not within the slice... -lpop: (self, name) - Remove and return the first item of the list ``name``... -llen: (self, name) - Return the length of the list ``name``... -lpushx: (self, name, value) - Push ``value`` onto the head of the list ``name`` if ``name`` exists... -linsert: (self, name, where, refvalue, value) - Insert ``value`` in list ``name`` either immediately before or after... -lindex: (self, name, index) - Return the item from list ``name`` at position ``index``... - -rpush: (self, name, *values) - Push ``values`` onto the tail of the list ``name``... -rpushx: (self, name, value) - Push ``value`` onto the tail of the list ``name`` if ``name`` exists... -rpop: (self, name) - Remove and return the last item of the list ``name``... -rpoplpush: (self, src, dst) - RPOP a value off of the ``src`` list and atomically LPUSH it... - -blpop: (self, keys, timeout=0) - LPOP a value off of the first non-empty list... -brpop: (self, keys, timeout=0) - RPOP a value off of the first non-empty list... -brpoplpush: (self, src, dst, timeout=0) - Pop a value off the tail of ``src``, push it on the head of ``dst``... -""" - -from collections.abc import MutableSequence - - -# TODO: Redis list type is more of a linked-list than a list. Better subclass deque? -
[docs]class RedisList(MutableSequence): - """ - An object to operate on Redis lists as if they were python lists - """ - - def __init__(self, source, name): - self._source = source - self._name = name - - def __len__(self): - return self._source.llen(self._name) - - def __getitem__(self, i): - if isinstance(i, int): - return self._source.lindex(self._name, i) - else: # assume it's a slice, and modify it to conform to inclusive Redis slicing - try: - if i.step is not None: - raise NotImplementedError( - "Slicing is so far only implemented without step" - ) - return self._source.lrange( - self._name, i.start or 0, i.stop - 1 - ) - except AttributeError: - raise KeyError( - f"Index should be an integer or a slice object. Was {i}" - ) - - def __iter__(self): - # TODO: Find a more efficient way to do this. - # In batches perhaps? But what's the optimal batch size? - # async fetching of the next value/batch while yielding the one that's already fetched? - for i in range(len(self)): - yield self[i] - - def __setitem__(self, i, v): - return self._source.lset(self._name, i, v) - - def __delitem__(self, i): # TODO: Implement - raise NotImplementedError( - "Don't know how to (efficiently/directly) delete a single middle item." - ) - -
[docs] def append(self, v): - return self._source.rpushx(self._name, v)
- -
[docs] def extend(self, iterable): - self._source.rpush(self._name, *iterable)
- -
[docs] def insert(self, i, v): # TODO: Implement - raise NotImplementedError("Might have to do with self._source.linsert")
- - -# TODO: Consider if should move to stores -# The following will probably be moved to stores, since it has serialization and indexinig logic -
[docs]class RedisCollection(RedisBytesCollection): - def __getitem__(self, k): - val_type = self._source.type(k) - if val_type == b"string": - return self._source.get(k) - elif val_type == b"list": - return RedisList(self._source, k)
- - -from typing import Iterable -from collections.abc import Mapping - - -
[docs]class RedisPersister(RedisCollection, RedisBytesPersister): - def __setitem__(self, k, v): - if isinstance(v, (str, bytes)): - return super().__setitem__(k, v) - elif isinstance(v, Iterable): - if k in self: - del self[k] - return self._source.rpush(k, *v)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/s3_w_boto3.html b/docs/_modules/py2store/persisters/s3_w_boto3.html deleted file mode 100644 index 87e83e8..0000000 --- a/docs/_modules/py2store/persisters/s3_w_boto3.html +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - - - py2store.persisters.s3_w_boto3 — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.s3_w_boto3

-from functools import partial
-from typing import Iterable, NewType
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-with ModuleNotFoundErrorNiceMessage():
-    from botocore.client import Config
-    from botocore.exceptions import ClientError
-    import boto3
-    from boto3.resources.base import ServiceResource
-
-    S3BucketType = NewType(
-        "S3BucketType", ServiceResource
-    )  # TODO: hack -- find how to import an actual Bucket type
-
-from py2store.base import KvReader, KvPersister, Collection
-
-
-
[docs]class NoSuchKeyError(KeyError): - pass
- - -encode_as_utf8 = partial(str, encoding="utf-8") - -DFLT_S3_OBJ_OF_DATA = encode_as_utf8 -DFLT_AWS_S3_ENDPOINT = "https://s3.amazonaws.com" -DFLT_BOTO_CLIENT_VERIFY = None -DFLT_SIGNATURE_VERSION = "s3v4" -DFLT_CONFIG = Config(signature_version=DFLT_SIGNATURE_VERSION) - - -
[docs]def get_s3_resource( - aws_access_key_id, - aws_secret_access_key, - endpoint_url=DFLT_AWS_S3_ENDPOINT, - verify=DFLT_BOTO_CLIENT_VERIFY, - config=DFLT_CONFIG, -): - """ - Get boto3 s3 resource. - :param aws_access_key_id: - :param aws_secret_access_key: - :param endpoint_url: - :param verify: - :param signature_version: - :return: - """ - return boto3.resource( - "s3", - endpoint_url=endpoint_url, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - verify=verify, - config=config, - )
- - -def get_s3_bucket( - name, - aws_access_key_id, - aws_secret_access_key, - endpoint_url=DFLT_AWS_S3_ENDPOINT, - verify=DFLT_BOTO_CLIENT_VERIFY, - config=DFLT_CONFIG, -): - s3 = get_s3_resource( - endpoint_url=endpoint_url, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - verify=verify, - config=config, - ) - return s3.Bucket(name) - - -# TODO: I wanted boto3.resources.factory.s3.ObjectSummary but couldn't find it -from boto3.resources.base import ServiceResource - - -def isdir(obj_summary: ServiceResource): - return obj_summary.size == 0 and obj_summary.key.endswith - - -def isfile(obj_summary: ServiceResource): - return not isdir(obj_summary) - - -def _resource_initializer( - self, - aws_access_key_id, - aws_secret_access_key, - endpoint_url=DFLT_AWS_S3_ENDPOINT, - verify=DFLT_BOTO_CLIENT_VERIFY, - config=DFLT_CONFIG, -): - self._source = get_s3_resource( - endpoint_url=endpoint_url, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - verify=verify, - config=config, - ) - - -from py2store.trans import cached_keys - - -
[docs]@cached_keys(keys_cache=list) -class S3ResourceCollection(Collection): - _source = None # to tell lint about it (TODO: Find less hacky way to talk to lint) - __init__ = _resource_initializer - - def __iter__(self) -> Iterable[S3BucketType]: - return self._source.buckets.all()
- - -from functools import wraps - - -# Pattern: reader from collection + mapping maker. -# TODO: Make a special factory out of the pattern -
[docs]class S3ResourceReader(S3ResourceCollection): - @wraps(S3ResourceCollection.__init__) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._source = {b.name: b for b in super().__iter__()} - - def __iter__(self) -> Iterable[S3BucketType]: - yield from self._source - - def __getitem__(self, k): - return S3BucketReader(self._source[k])
- - -
[docs]class S3BucketReader(KvReader): - def __init__(self, s3_bucket, filt=None): - self._source = s3_bucket - self.bucket_name = s3_bucket.name - self.filt = filt or {} - - def __getitem__(self, k): - try: - return k.get()["Body"].read() - except Exception as e: - raise NoSuchKeyError("Key wasn't found: {}".format(k)) - - def __iter__(self): - yield from self._source.objects.filter(**self.filt) - # return filter(isfile, self._source.objects.filter(**self.filt)) - - def __contains__(self, k): - try: - k.load() - return True # if all went well - except ClientError as e: - if e.response["Error"]["Code"] == "404": - # The object does not exist. - return False - else: - # Something else has gone wrong. - raise - - @classmethod - def from_s3_resource_kwargs( - cls, bucket_name, filt=None, resource_kwargs=None - ): - s3_resource = get_s3_resource(**(resource_kwargs or {})) - return cls.from_s3_resource(bucket_name, s3_resource, filt=filt) - - @classmethod - def from_s3_resource(cls, bucket_name, s3_resource, filt=None): - s3_bucket = s3_resource.Bucket(bucket_name) - return cls(s3_bucket, filt=filt) - - @classmethod - def from_s3_resource_kwargs_and_prefix( - cls, bucket_name, _prefix: str = "", resource_kwargs=None - ): - return cls.from_s3_resource_kwargs( - bucket_name, dict(Prefix=_prefix), resource_kwargs - ) - - @classmethod - def from_s3_resource_and_prefix(cls, bucket_name, s3_resource, _prefix=""): - return cls.from_s3_resource( - bucket_name, s3_resource, filt=dict(Prefix=_prefix) - )
- - -
[docs]class S3BucketRW(S3BucketReader, KvPersister): - def __setitem__(self, k, v): - return k.put(Body=v) - - def __delitem__(self, k): - try: - return k.delete() - except Exception as e: - if hasattr(e, "__name__"): - if e.__name__ == "NoSuchKey": - raise NoSuchKeyError("Key wasn't found: {}".format(k)) - raise # if you got so far
- - -# Planning on deprecating this one in favor of S3BucketRW -
[docs]class S3BucketPersister(KvPersister): - # Planning on deprecating this one in favor of S3BucketRW - def __init__(self, bucket_name: str, _s3_bucket, _prefix: str = ""): - self.bucket_name = bucket_name - self._s3_bucket = _s3_bucket # kept for back-compatibility and reference (so we know what source is) - self._source = _s3_bucket # to be the new consistent source - self._prefix = _prefix - - def __getitem__(self, k): - try: - return k.get()["Body"].read() - except Exception as e: - raise NoSuchKeyError("Key wasn't found: {}".format(k)) - - def __setitem__(self, k, v): - k.put(Body=v) - - def __delitem__(self, k): - try: - k.delete() - except Exception as e: - if hasattr(e, "__name__"): - if e.__name__ == "NoSuchKey": - raise NoSuchKeyError("Key wasn't found: {}".format(k)) - raise # if you got so far - - def __iter__(self): - return filter(isfile, self._source.objects.filter(Prefix=self._prefix)) - - def __contains__(self, k): - try: - k.load() - return True # if all went well - except ClientError as e: - if e.response["Error"]["Code"] == "404": - # The object does not exist. - return False - else: - # Something else has gone wrong. - raise - - @classmethod - def from_s3_resource_kwargs( - cls, bucket_name, _prefix: str = "", resource_kwargs=None - ): - s3_resource = get_s3_resource(**(resource_kwargs or {})) - return cls.from_s3_resource(bucket_name, s3_resource, _prefix=_prefix) - - @classmethod - def from_s3_resource(cls, bucket_name, s3_resource, _prefix=""): - s3_bucket = s3_resource.Bucket(bucket_name) - return cls(bucket_name, s3_bucket, _prefix=_prefix)
- - -# Experimental functions to be used to enhance methods for file system stores -# See File -try: - from boto3.resources.base import ServiceResource - - def isdir(obj_summary: ServiceResource): - return obj_summary.size == 0 and obj_summary.key.endswith - - -except: - pass -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/sql_w_sqlalchemy.html b/docs/_modules/py2store/persisters/sql_w_sqlalchemy.html deleted file mode 100644 index 2c2fc1a..0000000 --- a/docs/_modules/py2store/persisters/sql_w_sqlalchemy.html +++ /dev/null @@ -1,585 +0,0 @@ - - - - - - - - py2store.persisters.sql_w_sqlalchemy — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.sql_w_sqlalchemy

-from functools import partial
-
-import sqlalchemy
-
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-with ModuleNotFoundErrorNiceMessage():
-    from sqlalchemy import create_engine, Column, String, Table
-    from sqlalchemy.ext.declarative import declarative_base
-    from sqlalchemy.orm import sessionmaker
-    import sqlalchemy as db
-
-from collections.abc import Sequence
-from py2store.base import Persister
-from py2store.base import Collection, KvReader
-from py2store.util import lazyprop, lazyprop_w_sentinel
-
-DFLT_SQL_PORT = 1433
-DFLT_SQL_HOST = "localhost"
-
-
-# TODO: decorator to automatically retry (once) if the connection times out
-# --> from sqlalchemy.exc import OperationalError
-
-
-
[docs]class SqlTableRowsCollection(Collection): - """Base class wrapping an sql table. - It is Iterable (yields rows (as tuples)) and Sized (i.e. you can call len on it). - It's also a container, but brute-forced: should probably subclass if you want to perform `row in table` efficiently. - - Note: Be aware of how this object works if you're (1) talking to a table whose contents are changing dynamically. - * count_rows() will return the current count every time - * len returns the value of the _row_count lazyprop - * _row_count returns the length of the _rows cache if it exists, and if not, will return the live count_rows - * if len (therefore _row_count) is called before the object listed the rows (therefore filling it's cache), - it will return the current number of rows, but if at the time of listing rows, the number of different, - the number of rows will be updated to match number of rows cached. - * column_names is a cached property - """ - - _tmpl_count_rows_tmpl = "SELECT COUNT(*) FROM {table_name}" - _tmpl_describe_tmpl = "DESCRIBE {table_name}" - _tmpl_iter_tmpl = "SELECT * FROM {table_name}" - - def __init__( - self, connection, table_name, batch_size=2000, limit=int(1e16) - ): - self.connection = connection - self.table_name = table_name - self.iter_rows = partial( - iter_rows, - connection=connection, - table_name=table_name, - batch_size=batch_size, - limit=limit, - ) - - # Helpers ########################################################################################################## - # QUESTION: Should helpers (_describe, _columns, etc.) should be methods/properties/lazyprops, and hidden or not? - - def count_rows(self): - return self.connection.execute( - self._tmpl_count_rows_tmpl.format(table_name=self.table_name) - ).first()[0] - - @lazyprop - def _row_count(self): - if lazyprop_w_sentinel.cache_is_active(self, "_rows"): - return len(self._rows) - else: - return self.count_rows() - - def refresh_row_count(self): - del self._row_count # delete current - return ( - self._row_count - ) # return current row count (mainly to refresh the lazyprop - - def _describe(self): - return self.connection.execute( - self._tmpl_describe_tmpl.format(table_name=self.table_name) - ) - - @lazyprop - def column_names(self): - return tuple(x[0] for x in self._describe()) - - @lazyprop_w_sentinel - def _rows(self): - rows = list( - self.connection.execute( - self._tmpl_iter_tmpl.format(table_name=self.table_name) - ) - ) - self._row_count = len(rows) - return rows - - #################################################################################################################### - - def __iter__(self): - # Question: difference with `return iter(...)` for `for x in ...: yield x` options - # Question: Why is the construction of the connection.execute slow for big tables (not a real cursor it seems) - yield from self.iter_rows() - - def __len__(self): - return self._row_count - - def __getitem__(self, idx): # TODO: Make the ss[:-4] case work too - if isinstance(idx, slice): - start, stop, step = idx.start, idx.stop, idx.step - start = start or 0 - assert step is None, "__getitem__ doesn't handle stepped slices" - assert start >= 0, "slice start can't be negative" - - if stop: - assert ( - stop >= start - ), "slice stop must be at least the slice start" - return self.iter_rows(offset=start, limit=stop - start) - elif start: - return self.iter_rows(offset=start) - else: - return self.iter_rows() - elif isinstance(idx, int): - return self.iter_rows(offset=idx, limit=1) - - def __repr__(self): - return f"SqlTable(..., table_name={self.table_name})"
- - -
[docs]class SqlTableRowsSequence(SqlTableRowsCollection, Sequence): - def __getitem__(self, idx): - return list(super().__getitem__(idx))
- - -
[docs]class SqlDbCollection(Collection): - """A collection of sql tables names.""" - - def __init__(self, connection): - self.connection = connection - - @classmethod - def from_connection(cls, connection): - pass # TODO: Need to make object (with __new__) manually to do this? - - @classmethod - def from_engine(cls, engine): - connection = engine.connect() - o = cls(connection) - o.engine = engine - return o - - @classmethod - def from_uri(cls, uri): - engine = db.create_engine(uri) - o = cls.from_engine(engine) - o.uri = uri - return o - - @classmethod - def from_config_dict(cls, config_dict): - # handle defaults - config_dict = dict( - dict(host=DFLT_SQL_HOST, port=DFLT_SQL_PORT), **config_dict - ) - - # validate input - expected_keys = {"user", "pwd", "host", "port", "database"} - assert { - key for key in config_dict.keys() if key in expected_keys - } == expected_keys, "incomplete config" - - # make the uri - uri = "mysql+pymysql://{user}:{pwd}@{host}:{port}/{database}".format( - **config_dict - ) # connect to database - - # make an instance from uri - o = cls.from_uri(uri) - o.config_dict = config_dict - return o - - @classmethod - def from_configs( - cls, - database, - user="user", - pwd="password", - port=DFLT_SQL_PORT, - host=DFLT_SQL_HOST, - ): - return cls.from_config_dict( - dict(database=database, user=user, pwd=pwd, port=port, host=host) - ) - - def __iter__(self): - yield from (x[0] for x in self.connection.execute("show tables"))
- - -
[docs]class SqlDbReader(SqlDbCollection, KvReader): - """A KvReader of sql tables. Keys are table names and values are SqlTable objects""" - - def __getitem__(self, k): - return SqlTableRowsCollection(self.connection, k)
- - -# More explicit aliases -SqlAlchemyReader = SqlDbReader -SqlAlchemyDatabaseCollection = SqlDbCollection - - -# class DfSqlDbReader(SqlDbReader): -# def __getitem__(self, k): -# with ModuleNotFoundErrorNiceMessage(): -# import pandas as pd -# table = super().__getitem__(k) -# return pd.DataFrame(data=list(table), columns=table.column_names) - - -
[docs]class SQLAlchemyPersister(Persister): - - TYPE_INTEGER = sqlalchemy.INTEGER - TYPE_STRING = sqlalchemy.String - TYPE_BOOLEAN = sqlalchemy.BOOLEAN - TYPE_BLOB = sqlalchemy.BLOB - TYPE_TEXT = sqlalchemy.TEXT - - """ - A basic SQL DB persister written with SQLAlchemy. - """ - - def __init__( - self, - uri="sqlite:///my_sqlite.db", - collection_name="py2store_default_table", - key_fields={"_id": TYPE_INTEGER}, - data_fields={"data": TYPE_STRING}, - autocommit=True, - **db_kwargs, - ): - """ - :param uri: Uniform Resource Identifier of a database you would like to use. - Unix/Mac (note the four leading slashes) - sqlite:////absolute/path/to/foo.db - - Windows (note 3 leading forward slashes and backslash escapes) - sqlite:///C:\\absolute\\path\\to\\foo.db - - Or go for in-memory DB with NO PERSISTANCE: - sqlite:///:memory: - - Other options: - postgresql://user:password@localhost:5432/my_db - mysql://user:password@localhost/db - oracle://user:password:tiger@localhost:1521/sidname - - I.e. in general: - dialect+driver://username:password@host:port/database - - :param collection_name: name of the table to use, i.e. "my_table". - :param key_fields: indexed keys columns names. - :param data_fields: non-indexed data columns names. - :param autocommit: whether each data change should be instantly commited, or not. - It's off for context manager usecase (tbd). - - :param kwargs: any extra kwargs for SQLAlchemy engine to setup. - """ - self._key_fields = key_fields - self._data_fields = data_fields - self.autocommit = autocommit - - self.connection = None - self.table = None - self.session = None - - self.setup(uri, collection_name, **db_kwargs) - - def setup(self, db_uri, collection_name, **db_kwargs): - # Setup connection to our DB: - engine = create_engine(db_uri, **db_kwargs) - self.connection = engine.connect() - - # Create a table: - self.table = self._create_table(collection_name, engine) - - # Open ORM session: - self.session = sessionmaker(bind=engine)() - - def teardown(self): - self.session.close() - self.connection.close() - - def _create_table(self, table_name, engine): - """ Create our data table (if not there yet). """ - Base = declarative_base() - - table = Table( - table_name, - Base.metadata, - *[ - Column( - key, self._key_fields[key], primary_key=True, index=True - ) - for key in self._key_fields - ], - *[ - Column(name, self._data_fields[name]) - for name in self._data_fields - ], - ) - table.create(bind=engine, checkfirst=True) - - # Lets wrap our Table with Base so we'll be able to use ORM features later. - class DeclarativeTable(Base): - __table__ = table - - return DeclarativeTable - - @property - def query(self): - return self.session.query(self.table) - - def __getitem__(self, k): - doc = self.query.filter_by(**k).first() - # todo: think of intuitive way of selecting many by 1 (or some) of many keys - if not doc: - raise KeyError(f"No document found for query: {k}") - - return doc - - def __setitem__(self, k, v): - try: - doc = self[k] - except KeyError: - doc = self.table(**k, **v) - self.session.add(doc) - else: - for key, value in v.items(): - setattr(doc, key, value) - - if self.autocommit: - self.session.commit() - - def __delitem__(self, k): - doc = self[k] - self.session.delete(doc) - - if self.autocommit: - self.session.commit() - - def __iter__(self): - yield from self.query - - def __len__(self): - return self.query.count()
- - # def __del__(self): - # self.teardown() - - -
[docs]def iter_rows( - connection, table_name, batch_size=1000, offset=0, limit=int(1e12) -): - """Iterate the over the rows of a table. - The limit argument is mostly there to avoid an infinite loop, but can also be used to get ranges. - """ - stop_offset = limit + offset # to the range act like like sql limit - i = 0 - for offset in range(offset, stop_offset, batch_size): - if i >= limit: - break - r = connection.execute( - f"SELECT * FROM {table_name} LIMIT {batch_size} OFFSET {offset}" - ) - if r.rowcount: - for i, x in enumerate(r.fetchall(), i): - if i < limit: - yield x - else: - break - else: - break
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/ssh_persister.html b/docs/_modules/py2store/persisters/ssh_persister.html deleted file mode 100644 index 9c2f197..0000000 --- a/docs/_modules/py2store/persisters/ssh_persister.html +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - - py2store.persisters.ssh_persister — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.ssh_persister

-import os.path
-import stat
-
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-with ModuleNotFoundErrorNiceMessage():
-    import paramiko
-
-from py2store.base import Persister
-
-
-
[docs]def remote_mkdir(sftp, remote_directory): - """ - Change to this directory, recursively making new folders if needed. - returns: True if any folders were created. - """ - if remote_directory == "/": - # absolute path so change directory to root - sftp.chdir("/") - return False - if remote_directory == "": - # top-level relative directory must exist - return False - try: - sftp.chdir(remote_directory) # sub-directory exists - except IOError: - dirname, basename = os.path.split(remote_directory.rstrip("/")) - remote_mkdir(sftp, dirname) # make parent directories - sftp.mkdir(basename) # sub-directory missing, so created it - sftp.chdir(basename) - return True - - return False
- - -
[docs]class SshPersister(Persister): - """ - A basic ssh persister. - Keys must be names of files. - - >>> from py2store.persisters.ssh_persister import SshPersister - >>> s = SshPersister() - >>> k = 'foo' - >>> v = 'bar' - >>> for _key in s: - ... del s[_key] - >>> len(s) - 0 - >>> s[k] = v - >>> s[k] - 'bar' - >>> s.get(k) - 'bar' - >>> len(s) - 1 - >>> list(s.values()) - ['bar'] - >>> k in s - True - >>> del s[k] - >>> k in s - False - >>> len(s) - 0 - """ - - def __init__( - self, - user="stud", - password="stud", - url="10.1.103.201", - rootdir="./py2store", - encoding="utf8", - ): - self._ssh = paramiko.SSHClient() - self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - self._ssh.connect(url, username=user, password=password) - self._sftp = self._ssh.open_sftp() - self._rootdir = rootdir - self._encoding = encoding - remote_mkdir(self._sftp, self._rootdir) - - def __getitem__(self, k): - remote_file = self._sftp.file(k, mode="r") - data = remote_file.read().decode(self._encoding) - return data - - def __setitem__(self, k, v): - remote_file = self._sftp.file(k, mode="w") - remote_file.write(str(v).encode(self._encoding)) - - def __delitem__(self, k): - if len(k) > 0: - try: - self._sftp.remove(k) - except IOError: - raise KeyError(f"You can't removed that key: {k}") - else: - raise KeyError(f"You can't removed that key: {k}") - - def __contains__(self, k): - """ - Implementation of "k in self" check - """ - try: - fileattr = self._sftp.lstat(k) - if stat.S_ISREG(fileattr.st_mode): - return True - except Exception: - return False - - return False - - def __iter__(self): - files = self._sftp.listdir() - yield from files - - def __len__(self): - files = self._sftp.listdir() - return len(files) - - def __del__(self): - """ - Close ssh session when an object is deleted - """ - self._ssh.close()
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/persisters/w_aiofile.html b/docs/_modules/py2store/persisters/w_aiofile.html deleted file mode 100644 index 71b210e..0000000 --- a/docs/_modules/py2store/persisters/w_aiofile.html +++ /dev/null @@ -1,394 +0,0 @@ - - - - - - - - py2store.persisters.w_aiofile — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.persisters.w_aiofile

-import asyncio
-import os
-
-from py2store.base import KvReader, KvPersister
-from py2store.key_mappers.paths import mk_relative_path_store
-from py2store.filesys import (
-    FileCollection,
-    validate_key_and_raise_key_error_on_exception,
-)
-from py2store.util import ModuleNotFoundWarning
-
-with ModuleNotFoundWarning(f"Missing third-party package: aiofile"):
-    from aiofile import AIOFile
-
-_dflt_not_valid_error_msg = "Key not valid (usually because does not exist or access not permitted): {}"
-_dflt_not_found_error_msg = "Key not found: {}"
-
-
-
[docs]class AioFileBytesReader(FileCollection, KvReader): - _read_open_kwargs = dict(mode="rb") - - __getitem__ = None - - # @validate_key_and_raise_key_error_on_exception # TODO: does this also wrap the async? -
[docs] async def aget(self, k): # noqa - """ - Gets the bytes contents of the file k. - >>> import os - >>> filepath = __file__ - >>> dirpath = os.path.dirname(__file__) # path of the directory where I (the module file) am - >>> s = AioFileBytesReader(dirpath, max_levels=0) - >>> - >>> ####### Get the first 9 characters (as bytes) of this module ##################### - >>> t = await s.aget(filepath) - >>> t[:14] - b'import asyncio' - >>> - >>> ####### Test key validation ##################### - >>> await s.aget('not_a_valid_key') # this key is not valid since not under the dirpath folder - Traceback (most recent call last): - ... - filesys.KeyValidationError: 'Key not valid (usually because does not exist or access not permitted): not_a_valid_key' - >>> - >>> ####### Test further exceptions (that should be wrapped in KeyError) ##################### - >>> # this key is valid, since under dirpath, but the file itself doesn't exist (hopefully for this test) - >>> non_existing_file = os.path.join(dirpath, 'non_existing_file') - >>> try: - ... await s.aget(non_existing_file) - ... except KeyError: - ... print("KeyError (not FileNotFoundError) was raised.") - KeyError (not FileNotFoundError) was raised. - """ - - async with AIOFile(k, **self._read_open_kwargs) as fp: - v = ( - await fp.read() - ) # Question: Is it faster if we just did `return await fp.read(), instead of assign? - return v
- # with open(k, **self._read_open_kwargs) as fp: - # return fp.read() - - -
[docs]class AioFileBytesPersister(AioFileBytesReader, KvPersister): - _write_open_kwargs = dict(mode="wb") - -
[docs] @validate_key_and_raise_key_error_on_exception - async def asetitem(self, k, v): - """ - - >>> from py2store.persisters.w_aiofile import AioFileBytesPersister - >>> from py2store.filesys import mk_tmp_py2store_dir - >>> import os - >>> - >>> rootdir = mk_tmp_py2store_dir('test') - >>> rpath = lambda *p: os.path.join(rootdir, *p) - >>> s = AioFileBytesPersister(rootdir) - >>> k = rpath('foo') - >>> if k in s: - ... del s[k] # delete key if present - ... - >>> n = len(s) # number of items in store - >>> await s.asetitem(k, b'bar') - >>> assert len(s) == n + 1 # there's one more item in store - >>> assert k in s - >>> assert (await s[k]) == b'bar' - """ - async with AIOFile(k, **self._write_open_kwargs) as fp: - await fp.write(v) - await fp.fsync()
- - def __setitem__(self, k, v): - return asyncio.create_task(self.asetitem(k, v)) - - @validate_key_and_raise_key_error_on_exception - def __delitem__(self, k): - os.remove(k)
- - # @validate_key_and_raise_key_error_on_exception - # def __setitem__(self, k, v): - # with open(k, **self._write_open_kwargs) as fp: - # return fp.write(v) - - -RelPathAioFileBytesReader = mk_relative_path_store( - AioFileBytesReader, - prefix_attr="rootdir", - __name__="RelPathAioFileBytesReader", -) - - -
[docs]class AioFileStringReader(AioFileBytesReader): - _read_open_kwargs = dict(AioFileBytesReader._read_open_kwargs, mode="rt")
- - -
[docs]class AioFileStringPersister(AioFileBytesPersister): - _write_open_kwargs = dict( - AioFileBytesPersister._write_open_kwargs, mode="wt" - )
- - -RelPathFileStringReader = mk_relative_path_store( - AioFileStringReader, - prefix_attr="rootdir", - __name__="RelPathFileStringReader", -) - -########## The simple store we made during meeting ################################################################ - -# import os -# from collections.abc import MutableMapping -# from aiofile import AIOFile, Reader, Writer -# -# -# class SimpleFilePersister(MutableMapping): -# """Read/write (text or binary) data to files under a given rootdir. -# Keys must be absolute file paths. -# Paths that don't start with rootdir will be raise a KeyValidationError -# """ -# -# def __init__(self, rootdir, mode='t'): -# if not rootdir.endswith(os.path.sep): -# rootdir = rootdir + os.path.sep -# self.rootdir = rootdir -# assert mode in {'t', 'b', ''}, f"mode ({mode}) not valid: Must be 't' or 'b'" -# self.mode = mode -# -# # TODO: __getitem__ can't be async?!? -# # async def __getitem__(self, k): -# # async with AIOFile(k, 'r' + self.mode) as fp: -# # v = await fp.read() -# # return v -# -# def __getitem__(self, k): -# async with AIOFile(k, 'r' + self.mode) as fp: -# v = await fp.read() # Question: Is it faster if we just did `return await fp.read() -# return v -# -# async def asetitem(self, k, v): -# async with AIOFile(k, 'w' + self.mode) as fp: -# await fp.write(v) -# await fp.fsync() -# -# def __setitem__(self, k, v): -# # loop = asyncio.new_event_loop() -# # asyncio.set_event_loop(loop) -# return asyncio.create_task(self.asetitem(k, v)) -# -# def __delitem__(self, k): -# os.remove(k) -# -# def __contains__(self, k): -# """ Implementation of "k in self" check. -# Note: MutableMapping gives you this for free, using a try/except on __getitem__, -# but the following uses faster os functionality.""" -# return os.path.isfile(k) -# -# def __iter__(self): -# yield from filter(os.path.isfile, -# map(lambda x: os.path.join(self.rootdir, x), -# os.listdir(self.rootdir))) -# -# def __len__(self): -# """Note: There's system-specific faster ways to do this.""" -# count = 0 -# for _ in self.__iter__(): -# count += 1 -# return count -# -# def clear(self): -# """MutableMapping creates a 'delete all' functionality by default. Better disable it!""" -# raise NotImplementedError("If you really want to do that, loop on all keys and remove them one by one.") -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/scrap/new_gen_local.html b/docs/_modules/py2store/scrap/new_gen_local.html deleted file mode 100644 index 9b65ad7..0000000 --- a/docs/_modules/py2store/scrap/new_gen_local.html +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - py2store.scrap.new_gen_local — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.scrap.new_gen_local

-from dataclasses import dataclass
-import os
-from py2store.key_mappers.naming import StrTupleDict
-from py2store.key_mappers.str_utils import is_manual_format_string
-
-pjoin = os.path.join
-sep = os.path.sep
-
-
-
[docs]@dataclass -class ParametrizedPath: - def __init__( - self, - rootdir: str = "", - subpath: str = "{subpath}", - format_dict=None, - process_kwargs=None, - process_info_dict=None, - ): - self.rootdir = rootdir - assert is_manual_format_string( - subpath - ), "You need to use manual formatting (that is, name all your {} braces)" - self.subpath = subpath - self._keymap = StrTupleDict( - self.rjoin(self.subpath), - format_dict=format_dict, - process_kwargs=process_kwargs, - process_info_dict=process_info_dict, - ) - - def rjoin(self, *p): - return os.path.join(self.rootdir, *p) - - def to_tuple(self, path: str): - return self._keymap.info_tuple(path) - - def from_tuple(self, t: tuple): - return self._keymap.mk(*t) - - def to_dict(self, path: str): - return self._keymap.info_dict(path) - - def from_dict(self, d: str): - return self._keymap.mk(**d)
- - -from py2store.mixins import GetBasedContainerMixin -from py2store.persisters.local_files import ( - LocalFileRWD, - IterBasedSizedMixin, - iter_filepaths_in_folder_recursively, -) - - -
[docs]class Local( - ParametrizedPath, LocalFileRWD, IterBasedSizedMixin, GetBasedContainerMixin -): - def __init__( - self, rootdir, subpath="{subpath}", open_kwargs=None, **keymap_kws - ): - ParametrizedPath.__init__(self, rootdir, subpath, **keymap_kws) - open_kwargs = open_kwargs or {} - LocalFileRWD.__init__(self, **open_kwargs) - - def __iter__(self): - return iter_filepaths_in_folder_recursively(self.rootdir)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/selectors/mg_selectors.html b/docs/_modules/py2store/selectors/mg_selectors.html deleted file mode 100644 index 07924c7..0000000 --- a/docs/_modules/py2store/selectors/mg_selectors.html +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - - - py2store.selectors.mg_selectors — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.selectors.mg_selectors

-""" Selectors that use the mongo-query interface """
-
-from typing import Iterator
-
-from py2store.util import ModuleNotFoundErrorNiceMessage
-
-with ModuleNotFoundErrorNiceMessage():
-    import pandas as pd  # only used for pd.isnull (other option?)
-
-from py2store.selectors.mongoquery import Query
-
-
-def _print_docs(docs):
-    for doc in iter(docs):
-        print(doc)
-
-
-class Selection:
-    def __iter__(self) -> Iterator:
-        raise NotImplementedError(
-            "Needs to be implemented by a concrete class"
-        )
-
-    def __len__(self):
-        count = 0
-        for _ in self.__iter__():
-            count += 1
-        return count
-
-
-class Selector(Selection):
-    def select(self, selector) -> Selection:
-        raise NotImplementedError("Need to implement in concrete class")
-
-
-class FiltSelector(Selector):
-    def __init__(self, _docs, _filt=None):
-        self._docs = _docs
-        self._filt = _filt
-
-    def _filt_conjunction(self, filt: callable):
-        if self._filt is None:
-            return filt
-        else:
-            return lambda x: self._filt(x) and filt(x)
-
-    def __iter__(self):
-        return filter(self._filt, self._docs.__iter__())
-
-    def select(self, filt: callable) -> Selection:
-        return self.__class__(
-            _docs=self._docs, _filt=self._filt_conjunction(filt)
-        )
-
-
-
[docs]class MgDfSelector(Selector): - """ - >>> _docs = [ - ... {'bt': 0, 'tt': 5, 'tag': 'small'}, - ... {'bt': 10, 'tt': 15, 'tag': 'small'}, - ... {'bt': 20, 'tt': 25, 'tag': 'small'}, - ... {'bt': 30, 'tt': 35, 'tag': 'big'}, - ... {'bt': 40, 'tt': 45, 'tag': 'big'}, - ... {'bt': 50, 'tt': 55, 'tag': 'big'}] - >>> df_selector = MgDfSelector(_docs) - >>> len(df_selector) - 6 - >>> jdict = df_selector.to_jdict() - >>> import json - >>> json_str = json.dumps(jdict) - >>> df_selector_2 = MgDfSelector(json.loads(json_str)) - >>> len(df_selector_2) - 6 - >>> next(iter(df_selector)) - {'bt': 0, 'tag': 'small', 'tt': 5} - >>> next(iter(df_selector_2)) - {'bt': 0, 'tag': 'small', 'tt': 5} - """ - - def __init__(self, _df): - if isinstance(_df, list) and isinstance(_df[0], dict): - _df = pd.DataFrame(_df) - self._df = _df - - def __iter__(self): - return ( - {k: v for k, v in d.items() if not pd.isnull(v)} - for r, d in self._df.iterrows() - ) - - def __len__(self): - return len(self._df) - - def __contains__(self, item): - item = {k: v for k, v in item.items() if not pd.isnull(v)} - for existing_item in self: - if existing_item == item: - return True - return False - -
[docs] def select(self, selector) -> Selector: - """ - - :param selector: A mongo-like query of the underlying dataframe - :return: - >>> _docs = [ - ... {'bt': 0, 'tt': 5, 'tag': 'small'}, - ... {'bt': 10, 'tt': 15, 'tag': 'small'}, - ... {'bt': 20, 'tt': 25, 'tag': 'small'}, - ... {'bt': 30, 'tt': 35, 'tag': 'big'}, - ... {'bt': 40, 'tt': 45, 'tag': 'big'}, - ... {'bt': 50, 'tt': 55, 'tag': 'big'}] - >>> import pandas as pd - >>> selector = MgDfSelector(_df=pd.DataFrame(_docs)) - >>> len(selector) - 6 - >>> selection = selector.select({"tag": {"$eq": 'small'}}) - >>> len(selection) - 3 - >>> _print_docs(selection) - {'bt': 0, 'tag': 'small', 'tt': 5} - {'bt': 10, 'tag': 'small', 'tt': 15} - {'bt': 20, 'tag': 'small', 'tt': 25} - >>> selection = selector.select({'bt': {"$gte": 20}, 'tt': {"$lt": 45}}) - >>> _print_docs(selection) - {'bt': 20, 'tag': 'small', 'tt': 25} - {'bt': 30, 'tag': 'big', 'tt': 35} - """ - selector_file_func = Query(selector).match - lidx = list(map(selector_file_func, self._df.to_dict(orient="rows"))) - return self.__class__(self._df[lidx])
- # Below are just ideas towards a more general (source, selector, selection) framework - # selection = self.__class__(self._df[lidx]) - # selection._selector = selector - # return selection - - def to_jdict(self): - return list(self.__iter__()) - - @classmethod - def from_jdict(cls, jdict): - return cls(_df=pd.DataFrame(jdict))
- - -######################################################################################################################## -# Other versions of MgDfSelector that are more amenable to generalization... - - -
[docs]class MgDfSelector2(Selector): - """ - - :param selector: A mongo-like query of the underlying dataframe - :return: - >>> _docs = [ - ... {'bt': 0, 'tt': 5, 'tag': 'small'}, - ... {'bt': 10, 'tt': 15, 'tag': 'small'}, - ... {'bt': 20, 'tt': 25, 'tag': 'small'}, - ... {'bt': 30, 'tt': 35, 'tag': 'big'}, - ... {'bt': 40, 'tt': 45, 'tag': 'big'}, - ... {'bt': 50, 'tt': 55, 'tag': 'big'}] - >>> import pandas as pd - >>> selector = MgDfSelector2(pd.DataFrame(_docs)) - >>> len(selector) - 6 - >>> selection = selector.select({"tag": {"$eq": 'small'}}) - >>> len(selection) - 3 - >>> _print_docs(selection) - {'bt': 0, 'tag': 'small', 'tt': 5} - {'bt': 10, 'tag': 'small', 'tt': 15} - {'bt': 20, 'tag': 'small', 'tt': 25} - >>> selection = selector.select({'bt': {"$gte": 20}, 'tt': {"$lt": 45}}) - >>> _print_docs(selection) - {'bt': 20, 'tag': 'small', 'tt': 25} - {'bt': 30, 'tag': 'big', 'tt': 35} - """ - - def __init__(self, _docs, _filt=None): - self._docs = _docs - - def __iter__(self): - return (d.to_dict() for r, d in self._docs.iterrows()) - - def __len__(self): - return len(self._docs) - - def select(self, selector) -> Selector: - selector_file_func = Query(selector).match - lidx = list(map(selector_file_func, self._docs.to_dict(orient="rows"))) - return self.__class__(self._docs[lidx])
- - -
[docs]class LidxSelector(Selector): - """ See LidxSelectorDf for a 'concrete' subclass """ - - def __init__(self, _docs): - self._docs = _docs - - def __getitem__(self, k): - return self._docs.__getitem__(k) - - def _selector_func(self, selector): - return Query(selector).match - - def to_dict(self): - return dict(self._docs) # probably want to override - - def _selection_lidx(self, selector_file_func): - return list(map(selector_file_func, self.to_dict())) - - def select(self, selector): - selector_func = self._selector_func(selector) - selection_lidx = self._selection_lidx(selector_func) - return self.__class__(self[selection_lidx])
- - -
[docs]class LidxSelectorDf(LidxSelector): - """ - - :param selector: A mongo-like query of the underlying dataframe - :return: - >>> _docs = [ - ... {'bt': 0, 'tt': 5, 'tag': 'small'}, - ... {'bt': 10, 'tt': 15, 'tag': 'small'}, - ... {'bt': 20, 'tt': 25, 'tag': 'small'}, - ... {'bt': 30, 'tt': 35, 'tag': 'big'}, - ... {'bt': 40, 'tt': 45, 'tag': 'big'}, - ... {'bt': 50, 'tt': 55, 'tag': 'big'}] - >>> import pandas as pd - >>> selector = LidxSelectorDf(pd.DataFrame(_docs)) - >>> len(selector) - 6 - >>> selection = selector.select({"tag": {"$eq": 'small'}}) - >>> len(selection) - 3 - >>> _print_docs(selection) - {'bt': 0, 'tag': 'small', 'tt': 5} - {'bt': 10, 'tag': 'small', 'tt': 15} - {'bt': 20, 'tag': 'small', 'tt': 25} - >>> selection = selector.select({'bt': {"$gte": 20}, 'tt': {"$lt": 45}}) - >>> _print_docs(selection) - {'bt': 20, 'tag': 'small', 'tt': 25} - {'bt': 30, 'tag': 'big', 'tt': 35} - """ - - def __len__(self): - return len(self._docs) - - def __iter__(self): - return (d.to_dict() for r, d in self._docs.iterrows()) - - def to_dict(self): - return self._docs.to_dict(orient="rows") - - def _selector_func(self, selector): - return Query(selector).match
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/selectors/mongoquery.html b/docs/_modules/py2store/selectors/mongoquery.html deleted file mode 100644 index b8ffff0..0000000 --- a/docs/_modules/py2store/selectors/mongoquery.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - py2store.selectors.mongoquery — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.selectors.mongoquery

-"""
-Transform mongo-like selector dicts (filters) into boolean functions that implement the condition
-
-Modified from mongoquery (https://github.com/kapouille/mongoquery)
-
-mongoquery provides a straightforward API to match Python objects against
-MongoDB Query Language queries.
-"""
-
-import re
-from collections.abc import Sequence, Mapping
-from six import string_types
-
-
-
[docs]class QueryError(Exception): - """ Query error exception """ - - pass
- - -class _Undefined(object): - # pylint: disable=too-few-public-methods - pass - - -
[docs]def is_non_string_sequence(entry): - """ Returns True if entry is a Python sequence iterable, and not a string """ - return isinstance(entry, Sequence) and not isinstance(entry, str)
- - -
[docs]class Query(object): - """ The Query class is used to match an object against a MongoDB-like query """ - - # pylint: disable=too-few-public-methods - def __init__(self, definition): - self._definition = definition - -
[docs] def match(self, entry): - """ Matches the entry object against the query specified on instanciation """ - return self._match(self._definition, entry)
- - def _match(self, condition, entry): - if isinstance(condition, Mapping): - return all( - self._process_condition(sub_operator, sub_condition, entry) - for sub_operator, sub_condition in condition.items() - ) - if is_non_string_sequence(entry): - return condition in entry - return condition == entry - - def _extract(self, entry, path): - if not path: - return entry - if entry is None: - return entry - if is_non_string_sequence(entry): - try: - index = int(path[0]) - return self._extract(entry[index], path[1:]) - except ValueError: - return [self._extract(item, path) for item in entry] - elif isinstance(entry, Mapping) and path[0] in entry: - return self._extract(entry[path[0]], path[1:]) - else: - return _Undefined() - - def _path_exists(self, operator, condition, entry): - keys_list = list(operator.split(".")) - for i, k in enumerate(keys_list): - if isinstance(entry, Sequence) and not k.isdigit(): - for elem in entry: - operator = ".".join(keys_list[i:]) - if ( - self._path_exists(operator, condition, elem) - == condition - ): - return condition - return not condition - elif isinstance(entry, Sequence): - k = int(k) - try: - entry = entry[k] - except (TypeError, IndexError, KeyError): - return not condition - return condition - - def _process_condition(self, operator, condition, entry): - if isinstance(condition, Mapping) and "$exists" in condition: - if isinstance(operator, string_types) and operator.find(".") != -1: - return self._path_exists(operator, condition["$exists"], entry) - elif condition["$exists"] != (operator in entry): - return False - elif tuple(condition.keys()) == ("$exists",): - return True - if isinstance(operator, str): - if operator.startswith("$"): - try: - return getattr(self, "_" + operator[1:])(condition, entry) - except AttributeError: - raise QueryError( - "{!r} operator isn't supported".format(operator) - ) - else: - try: - extracted_data = self._extract(entry, operator.split(".")) - except IndexError: - extracted_data = _Undefined() - else: - if operator not in entry: - return False - extracted_data = entry[operator] - return self._match(condition, extracted_data) - - ################## - # Common operators - ################## - - @staticmethod - def _not_implemented(*_): - raise NotImplementedError - - @staticmethod - def _noop(*_): - return True - - ###################### - # Comparison operators - ###################### - - @staticmethod - def _eq(condition, entry): - try: - return entry == condition - except TypeError: - return False - - @staticmethod - def _gt(condition, entry): - try: - return entry > condition - except TypeError: - return False - - @staticmethod - def _gte(condition, entry): - try: - return entry >= condition - except TypeError: - return False - - @staticmethod - def _in(condition, entry): - if is_non_string_sequence(condition): - for elem in condition: - if is_non_string_sequence(entry) and elem in entry: - return True - elif not is_non_string_sequence(entry) and elem == entry: - return True - return False - else: - raise TypeError("condition must be a list") - - @staticmethod - def _lt(condition, entry): - try: - return entry < condition - except TypeError: - return False - - @staticmethod - def _lte(condition, entry): - try: - return entry <= condition - except TypeError: - return False - - @staticmethod - def _ne(condition, entry): - return entry != condition - - def _nin(self, condition, entry): - return not self._in(condition, entry) - - ################### - # Logical operators - ################### - - def _and(self, condition, entry): - if isinstance(condition, Sequence): - return all( - self._match(sub_condition, entry) - for sub_condition in condition - ) - raise QueryError( - "$and has been attributed incorrect argument {!r}".format( - condition - ) - ) - - def _nor(self, condition, entry): - if isinstance(condition, Sequence): - return all( - not self._match(sub_condition, entry) - for sub_condition in condition - ) - raise QueryError( - "$nor has been attributed incorrect argument {!r}".format( - condition - ) - ) - - def _not(self, condition, entry): - return not self._match(condition, entry) - - def _or(self, condition, entry): - if isinstance(condition, Sequence): - return any( - self._match(sub_condition, entry) - for sub_condition in condition - ) - raise QueryError( - "$nor has been attributed incorrect argument {!r}".format( - condition - ) - ) - - ################### - # Element operators - ################### - - @staticmethod - def _type(condition, entry): - # TODO: further validation to ensure the right type - # rather than just checking - bson_type = { - 1: float, - 2: str, - 3: Mapping, - 4: Sequence, - 5: bytearray, - 7: str, # object id (uuid) - 8: bool, - 9: str, # date (UTC datetime) - 10: type(None), - 11: str, # regex, - 13: str, # Javascript - 15: str, # JavaScript (with scope) - 16: int, # 32-bit integer - 17: int, # Timestamp - 18: int, # 64-bit integer - } - bson_alias = { - "double": 1, - "string": 2, - "object": 3, - "array": 4, - "binData": 5, - "objectId": 7, - "bool": 8, - "date": 9, - "null": 10, - "regex": 11, - "javascript": 13, - "javascriptWithScope": 15, - "int": 16, - "timestamp": 17, - "long": 18, - } - - if condition == "number": - return any( - [ - isinstance(entry, bson_type[bson_alias[alias]]) - for alias in ["double", "int", "long"] - ] - ) - - # resolves bson alias, or keeps original condition value - condition = bson_alias.get(condition, condition) - - if condition not in bson_type: - raise QueryError( - "$type has been used with unknown type {!r}".format(condition) - ) - - return isinstance(entry, bson_type.get(condition)) - - _exists = _noop - - ###################### - # Evaluation operators - ###################### - - @staticmethod - def _mod(condition, entry): - return entry % condition[0] == condition[1] - - @staticmethod - def _regex(condition, entry): - if not isinstance(entry, str): - return False - try: - regex = re.match( - r"\A/(.+)/([imsx]{,4})\Z", condition, flags=re.DOTALL - ) - except TypeError: - raise QueryError( - "{!r} is not a regular expression " - "and should be a string".format(condition) - ) - - flags = 0 - if regex: - options = regex.group(2) - for option in options: - flags |= getattr(re, option.upper()) - exp = regex.group(1) - else: - exp = condition - - try: - match = re.search(exp, entry, flags=flags) - except Exception as error: - raise QueryError( - "{!r} failed to execute with error {!r}".format( - condition, error - ) - ) - return bool(match) - - _options = _text = _where = _not_implemented - - ################# - # Array operators - ################# - - def _all(self, condition, entry): - return all(self._match(item, entry) for item in condition) - - def _elemMatch(self, condition, entry): - # pylint: disable=invalid-name - if not isinstance(entry, Sequence): - return False - return any( - all( - self._process_condition(sub_operator, sub_condition, element) - for sub_operator, sub_condition in condition.items() - ) - for element in entry - ) - - @staticmethod - def _size(condition, entry): - if not isinstance(condition, int): - raise QueryError( - "$size has been attributed incorrect argument {!r}".format( - condition - ) - ) - - if is_non_string_sequence(entry): - return len(entry) == condition - - return False - - #################### - # Comments operators - #################### - - _comment = _noop
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/serializers/jsonization.html b/docs/_modules/py2store/serializers/jsonization.html deleted file mode 100644 index d833bdf..0000000 --- a/docs/_modules/py2store/serializers/jsonization.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - py2store.serializers.jsonization — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.serializers.jsonization

-from functools import partial
-import json
-import marshal
-
-
-
[docs]def mk_marshal_rw_funcs( - **kwargs, -): # TODO: Check actual arguments for marshal load and dump - """Generates a reader and writer using marshal. That is, a pair of parametrized loads and dumps - - >>> read, write = mk_marshal_rw_funcs() - >>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2, dict]}} - >>> serialized_d = write(d) - >>> deserialized_d = read(serialized_d) - >>> assert d == deserialized_d - """ - return partial(marshal.dumps, **kwargs)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/serializers/pickled.html b/docs/_modules/py2store/serializers/pickled.html deleted file mode 100644 index 7f6ed3e..0000000 --- a/docs/_modules/py2store/serializers/pickled.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - - - py2store.serializers.pickled — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.serializers.pickled

-"""
-functions to pickle objects
-"""
-import pickle
-import marshal
-from functools import partial
-
-rw_funcs_maker_for = dict()
-
-
-# TODO: Make (in a different module) a factory to encapsulate the common pattern of the next three functions, and others
-
-
-
[docs]def mk_pickle_rw_funcs( - fix_imports=True, - protocol=None, - pickle_encoding='ASCII', - pickle_errors='strict', -): - """Generates a reader and writer using pickle. That is, a pair of parametrized loads and dumps - - >>> read, write = mk_pickle_rw_funcs() - >>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2, dict]}} - >>> serialized_d = write(d) - >>> deserialized_d = read(serialized_d) - >>> assert d == deserialized_d - """ - return ( - partial( - pickle.loads, - fix_imports=fix_imports, - encoding=pickle_encoding, - errors=pickle_errors, - ), - partial(pickle.dumps, protocol=protocol, fix_imports=fix_imports), - )
- - -rw_funcs_maker_for['pickle'] = mk_pickle_rw_funcs - - -
[docs]def mk_marshal_rw_funcs( - **kwargs, -): # TODO: Check actual arguments for marshal load and dump - """Generates a reader and writer using marshal. That is, a pair of parametrized loads and dumps - - >>> read, write = mk_marshal_rw_funcs() - >>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2]}} - >>> serialized_d = write(d) - >>> deserialized_d = read(serialized_d) - >>> assert d == deserialized_d - """ - return (partial(marshal.loads, **kwargs), partial(marshal.dumps, **kwargs))
- - -rw_funcs_maker_for['marshal'] = mk_marshal_rw_funcs - -##### Extras (requiring some third-party packages ###################################################################### - -from py2store.util import ModuleNotFoundIgnore - -with ModuleNotFoundIgnore(): - import dill - - def mk_dill_rw_funcs( - ignore=None, protocol=None, byref=None, fmode=None, recurse=None - ): - """Generates a reader and writer using dill. That is, a pair of parametrized loads and dumps - - >>> read, write = mk_dill_rw_funcs() - >>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2, dict]}} - >>> serialized_d = write(d) - >>> deserialized_d = read(serialized_d) - >>> assert d == deserialized_d - """ - - return ( - partial(dill.loads, ignore=ignore), - partial( - dill.dumps, - protocol=protocol, - byref=byref, - fmode=fmode, - recurse=recurse, - ), - ) - - rw_funcs_maker_for['dill'] = mk_dill_rw_funcs - -# class PickleMixin: -# """Local files store with pickle serialization""" -# -# def __init__(self, path_format, -# fix_imports=True, protocol=None, pickle_encoding='ASCII', pickle_errors='strict', -# **open_kwargs): -# super().__init__(path_format, mode='b', **open_kwargs) -# self._loads = partial(pickle.loads, fix_imports=fix_imports, encoding=pickle_encoding, errors=pickle_errors) -# self._dumps = partial(pickle.dumps, protocol=protocol, fix_imports=fix_imports) -# -# def __getitem__(self, k): -# return self._loads(super().__getitem__(k)) -# -# def __setitem__(self, k, v): -# return super().__setitem__(k, self._dumps(v)) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/serializers/regular_panel_data.html b/docs/_modules/py2store/serializers/regular_panel_data.html deleted file mode 100644 index fc6e3ed..0000000 --- a/docs/_modules/py2store/serializers/regular_panel_data.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - py2store.serializers.regular_panel_data — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.serializers.regular_panel_data

-"""Serialization for regular panel data (audio, regular-tick time-series, etc.).
-"""
-
-import numpy as np
-import soundfile as sf
-from io import BytesIO
-
-
-
[docs]class WrongSampleRate(ValueError): - """ To be raised when the sample rate is not the one that's expected. """ - - pass
- - -
[docs]class WrongSerializationParams(ValueError): - pass
- - -
[docs]def mk_reader_and_writer( - sr: int, - format="RAW", - subtype="PCM_16", - dtype="int16", - channels: int = 1, - endian=None, - always_2d=False, -): - """Makes a (bijective) pair of numerical arrays serializer and deserializer functions. - A function returning bijective panel data reader and writer functions with simple interfaces (all parametrizations - are fixed): `read(source)` and `write(source, data)`. - The writer and reader are essentially matrix (or array) serializers and deserializers respectively, - using the same serialization protocols as waveform (into PCM, WAV, etc.) - - Args: - sr: Sample rate. When the serialization format handles this information (e.g. WAV format) - the sample rate is actually written (in WAV, in the header bytes), and asserted on reads - (that is, if you read a WAV file that doesn't have that exact sample rate in it's header, a - WrongSampleRate error will be raised. - When the serialization format doesn't (e.g. RAW format (a.k.a. PCM)), it is ignored both on reads and writes - format: 'RAW', 'WAV' and others (see soundfile.available_formats() for a full list) - subtype: 'FLOAT', 'PCM_16' and others (see soundfile.available_subtypes() for a full list) - dtype: 'float64', 'float32', 'int32', 'int16' - channels: Number of channels (should equal the number of columns of the data matrices that will be - serialized -- or 1 if the data is flat) - endian: see soundfile documentation ({'FILE', 'LITTLE', 'BIG', 'CPU'}, sometimes optional) - always_2d: By default, reading a mono sound file will return a one-dimensional array. With always_2d=True, - data is always returned as a two-dimensional array, even if the data has only one channel. - - Returns: - read(k), write(k, v) functions - - >>> n_channels, dtype = 1, 'float64' - >>> read, write = mk_reader_and_writer(sr=44100, channels=n_channels, subtype='FLOAT', format='RAW', dtype=dtype) - >>> data = _random_matrix(n_channels=n_channels, dtype=dtype) - >>> _test_data_write_read(data, writer=write, reader=read) - - >>> n_channels, dtype = 4, 'int16' - >>> read, write = mk_reader_and_writer(sr=2, channels=n_channels, subtype='PCM_16', format='RAW', dtype=dtype) - >>> data = _random_matrix(n_channels=n_channels, dtype=dtype) - >>> _test_data_write_read(data, writer=write, reader=read) - """ - if not sf.check_format(format, subtype, endian): - raise WrongSerializationParams( - f"Not a valid combo: format={format}, subtype={subtype}, endian={endian}" - ) - - subtype = subtype or sf.default_subtype(format) - - if format == "RAW": - - def read(k): - wf, _ = sf.read( - k, - samplerate=sr, - channels=channels, - format=format, - subtype=subtype, - dtype=dtype, - endian=endian, - always_2d=always_2d, - ) - return wf - - else: - - def read(k): - wf, sr_read = sf.read(k, dtype=dtype, always_2d=always_2d) - if sr != sr_read: - raise WrongSampleRate( - f"Sample rate was {sr_read}: Expected {sr}" - ) - return wf - - def write(k, v): - return sf.write( - k, v, samplerate=sr, format=format, subtype=subtype, endian=endian - ) - - # add some attributes to the functions, for diagnosis purposes - read.sr = sr - read.dtype = dtype - read.format = format - write.sr = sr - write.format = format - write.subtype = subtype - - return read, write
- - -# TODO: Make it pytest compliant (problem with fixture) -def _test_data_write_read(data, writer, reader): - b = BytesIO() - writer(b, data) - b.seek(0) # rewind - read_data = reader(b) - if isinstance(read_data, tuple): - read_data, read_sr = read_data - assert np.allclose(read_data, data), "np.allclose(read_data, data)" - # assert read_sr == sr, 'read_sr == sr' - - -def _random_matrix( - n_samples=100, n_channels=1, value_range=(-2000, 2000), dtype="float64" -): - """ - Make a random matrix with n_samples rows and n_channels columns, with numbers drawn randomly from - the value_range interval - Args: - n_samples: number of rows in the matrix - n_channels: number of columns in the matrix (if n_channels=1, the function will output a flat array) - value_range: the range to pick from - - Returns: - a matrix (i.e. array of arrays) or a flat array (if n_channels==1). - """ - - if isinstance(value_range, (int, float)): - interval_length = value_range - value_range = (-interval_length / 2, interval_length / 2) - else: - interval_length = value_range[1] - value_range[0] - - data = ( - np.random.rand(n_samples, n_channels) * interval_length - ) + value_range[0] - - if n_channels == 1: - data = np.ravel(data) - - return data.astype(dtype) - - -def _random_data_and_serialization_params( - n_samples=100, n_channels=1, value_range=(-2000, 2000), dtype="float64" -): - """ Get random data and serialization params (i.e. how to map to bytes)""" - raise NotImplementedError("Not implemented yet") - - -if __name__ == "__main__": - n_channels, dtype = 1, "float64" - read, write = mk_reader_and_writer( - sr=44100, - channels=n_channels, - subtype="FLOAT", - format="RAW", - dtype=dtype, - ) - data = _random_matrix(n_channels=n_channels, dtype=dtype) - _test_data_write_read(data, writer=write, reader=read) - - n_channels, dtype = 1, "int16" - read, write = mk_reader_and_writer( - sr=44100, - channels=n_channels, - subtype="PCM_16", - format="RAW", - dtype=dtype, - ) - data = _random_matrix(n_channels=n_channels, dtype=dtype) - _test_data_write_read(data, writer=write, reader=read) - - n_channels, dtype = 1024, "float32" - read, write = mk_reader_and_writer( - sr=10, channels=n_channels, subtype="FLOAT", format="RAW", dtype=dtype - ) - data = _random_matrix(n_channels=n_channels, dtype=dtype) - _test_data_write_read(data, writer=write, reader=read) - - n_channels = int( - 2 ** 10 - 1 - ) # one more would be too much for format='WAV' - read, write = mk_reader_and_writer( - sr=1, channels=n_channels, subtype="FLOAT", format="WAV" - ) - data = _random_matrix(n_channels=n_channels) - _test_data_write_read(data, writer=write, reader=read) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/slib/s_configparser.html b/docs/_modules/py2store/slib/s_configparser.html deleted file mode 100644 index b5e18e2..0000000 --- a/docs/_modules/py2store/slib/s_configparser.html +++ /dev/null @@ -1,626 +0,0 @@ - - - - - - - - py2store.slib.s_configparser — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.slib.s_configparser

-from configparser import ConfigParser
-from configparser import BasicInterpolation, ExtendedInterpolation
-from functools import wraps
-from io import BytesIO, StringIO
-
-from py2store.trans import kv_wrap_persister_cls
-
-# from py2store.utils.signatures import Sig
-
-_test_config_str = """[Simple Values]
-key=value
-spaces in keys=allowed
-spaces in values=allowed as well
-spaces around the delimiter = obviously
-you can also use : to delimit keys from values
-
-[All Values Are Strings]
-values like this: 1000000
-or this: 3.14159265359
-are they treated as numbers? : no
-integers, floats and booleans are held as: strings
-can use the API to get converted values directly: true
-
-[Multiline Values]
-chorus: I'm a lumberjack, and I'm okay
-    I sleep all night and I work all day
-
-[No Values]
-key_without_value
-empty string value here =
-
-[You can use comments]
-# like this
-; or this
-
-# By default only in an empty line.
-# Inline comments can be harmful because they prevent users
-# from using the delimiting characters as parts of values.
-# That being said, this can be customized.
-
-    [Sections Can Be Indented]
-        can_values_be_as_well = True
-        does_that_mean_anything_special = False
-        purpose = formatting for readability
-        multiline_values = are
-            handled just fine as
-            long as they are indented
-            deeper than the first line
-            of a value
-        # Did I mention we can indent comments, too?
-"""
-
-
-def persist_after_operation(method_func):
-    @wraps(method_func)
-    def _method_func(self, *args, **kwargs):
-        output = method_func(self, *args, **kwargs)
-        self.persist()
-        return output
-
-    return _method_func
-
-
-
[docs]def super_and_persist(super_cls, method_name): - """ - To be able to do this: - ``` - __setitem__ = super_and_persist(ConfigParser, '__setitem__') - __delitem__ = super_and_persist(ConfigParser, '__delitem__') - ``` - in your class definition block. - - I thought I needed to wrap more method this way, but as it turns out, I might not, - so I prefer open code. - """ - - @persist_after_operation - @wraps(getattr(super_cls, method_name)) - def method_func(self, *args, **kwargs): - method_obj = getattr(super(ConfigStore, self), method_name) - return method_obj(*args, **kwargs) - - return method_func
- - -ConfigParserStore = kv_wrap_persister_cls( - ConfigParser, name="ConfigParserStore" -) - - -# TODO: ConfigParser is already a mapping, but pros/cons of subclassing? -# For instance, it has it's get method already, but it is not consistent with the get of collections.abc.Mapping -# TODO: Extend to a KvPersister (include __setitem__ and __delitem__) -# Relevant methods: add_section, write, remove_section. Need to decide on auto-persistence. -# In fact, the reader is already a writer (from ConfigParser), but need to manage persistence. -
[docs]class ConfigStore(ConfigParserStore): - r"""Persister (read, write, delete) for ini configs. - - You can read ini formated configurations with ConfigStore (though if you want to - just read, you should use ConfigReader instead -- since ConfigReader disables - write and delete operations. - - See ConfigReader for more examples of how to use ConfigStore. - We'll mainly focus on write and delete operations here. - - >>> import os - >>> from py2store.slib.s_configparser import ConfigStore, ConfigReader - >>> ini_filepath = 'config_store_test.ini' - >>> if os.path.isfile(ini_filepath): - ... os.remove(ini_filepath) - >>> - >>> os.path.isfile(ini_filepath) # File doesn't exist - False - >>> - >>> s = ConfigStore(ini_filepath) - >>> list(s) # There's always a default (by default empty) - ['DEFAULT'] - >>> - >>> os.path.isfile(ini_filepath) # But the file still doesn't exist (the DEFAULT is virtual) - False - >>> - >>> # Now let's make a config - >>> s['nothing'] = {'special': 'about', 'number': 42} - >>> list(s) - ['DEFAULT', 'nothing'] - >>> - >>> os.path.isfile(ini_filepath) # But NOW the file exists (ConfigStore will automatically write to file) - True - >>> s['add'] = {'more': 'sections'} - >>> list(s) - ['DEFAULT', 'nothing', 'add'] - - >>> # and yes, that config can now be read - >>> config_reader = ConfigReader(ini_filepath) - >>> list(config_reader) - ['DEFAULT', 'nothing', 'add'] - >>> - >>> config_reader['nothing'] - <Section: nothing> - >>> - >>> dict(config_reader['nothing']) # note that 42 is now a string (that's the ini format for you!) - {'special': 'about', 'number': '42'} - >>> dict(config_reader['DEFAULT']) # and DEFAULT is empty - {} - - You can delete sections - >>> del s['add'] - - But you'll need to refresh your reader to see the effect. - >>> list(config_reader) - ['DEFAULT', 'nothing', 'add'] - >>> config_reader = ConfigReader(ini_filepath) - >>> list(config_reader) - ['DEFAULT', 'nothing'] - - You can use `update` to write several sections at the same time. - Note that existing sections will be completely overwritten. - >>> s.update({'nothing': {'like': 'you'}, 'new_section': {'a': 'b', 'c': 'd'}}) - >>> ConfigReader(ini_filepath).to_dict() - {'DEFAULT': {}, 'nothing': {'like': 'you'}, 'new_section': {'a': 'b', 'c': 'd'}} - - **Warning: On the other hand, updating a section will not persist the updates** - - Updates are automatically persisted at the top level, as shown in the example above. - This means you can change a section entirely, but partial updates of a section - will not be persisted. - - You'll see the updated section in the store. - >>> s['nothing'].update({'something': 'else'}) - >>> dict(s['nothing']) - {'like': 'you', 'something': 'else'} - - But it's not automatically persisted - >>> dict(ConfigReader(ini_filepath)['nothing']) - {'like': 'you'} - - ... unless you ask for it explicitly - >>> s.persist() - >>> dict(ConfigReader(ini_filepath)['nothing']) - {'like': 'you', 'something': 'else'} - - # TODO: Could make section updates auto-persistent by wrapping configparser.SectionProxy - - For your convenience, the ConfigStore is also a context manager, that will, - you guessed, persist stuff when (and only when) you exit it. - - >>> ConfigReader(ini_filepath).to_dict() - {'DEFAULT': {}, 'nothing': {'like': 'you', 'something': 'else'}, 'new_section': {'a': 'b', 'c': 'd'}} - >>> with ConfigStore(ini_filepath) as s: - ... del s['new_section'] # that's usually immediately persisted. This time, it'll wait to be - ... del s['nothing']['something'] # delete the something field of nothing section - ... s['nothing'].update({'like': 'that', 'ever': 'happened'}) # update 'like' config and add an 'ever' one - >>> ConfigReader(ini_filepath).to_dict() - {'DEFAULT': {}, 'nothing': {'like': 'that', 'ever': 'happened'}} - - """ - space_around_delimiters = True - BasicInterpolation = BasicInterpolation - ExtendedInterpolation = ExtendedInterpolation - - # @Sig.from_objs(['source', ConfigParser.__init__, ('target_kind', None)]) # need to add source and target_kind - def __init__( - self, - source, - *, - defaults=None, - dict_type=dict, - allow_no_value=False, - target_kind=None, - **more_config_parser_kwargs, - ): - - super().__init__(defaults, dict_type, allow_no_value, **more_config_parser_kwargs) - - self._within_context_manager = False - - if isinstance(source, str): - if "\n" in source: - self.read_string(source) - source_kind = "string" - else: - self.read(source) - source_kind = "filepath" - elif isinstance(source, bytes): - self.read_string(source.decode()) - source_kind = "bytes" - elif isinstance(source, dict): - self.read_dict(source) - source_kind = "dict" - elif hasattr(source, "read"): - self.read_file(source) - source_kind = "stream" - else: - self.read(source) - source_kind = "unknown" - self.source = source - self.source_kind = source_kind - self.target_kind = target_kind or source_kind - - def to_dict(self): - return { - section: dict(section_contents) - for section, section_contents in self.items() - } - -
[docs] def persist(self): - """Persists the data (if not in a context manager). - Persists means to call - """ - if not self._within_context_manager: - if self.target_kind == "filepath": - with open(self.source, "w") as fp: - return self.write(fp, self.space_around_delimiters) - else: - if self.target_kind == "stream": - target = self.source - return self.write(target, self.space_around_delimiters) - elif self.target_kind in {"string", "bytes"}: - string_target = StringIO() - self.write(string_target, self.space_around_delimiters) - string_target.seek(0) - string_data = string_target.read() - if self.target_kind == "string": - return string_data - elif self.target_kind == "bytes": - return string_data.encode() - else: - raise ValueError( - f"Unknown target_kind: {self.target_kind}" - ) - elif self.target_kind == "dict": - return self.to_dict() - else: - raise ValueError( - f"Unknown target_kind: {self.target_kind}" - )
- - def __enter__(self): - self._within_context_manager = True - return self - - def __exit__(self, *exc_details): - self._within_context_manager = False - return self.persist() - - @persist_after_operation - def __setitem__(self, k, v): - super(ConfigStore, self).__setitem__(k, v) - - @persist_after_operation - def __delitem__(self, k): - super(ConfigStore, self).__delitem__(k)
- - # __setitem__ = super_and_persist(ConfigParser, '__setitem__') - # __delitem__ = super_and_persist(ConfigParser, '__delitem__') - - -
[docs]class ConfigReader(ConfigStore): - r"""A KvReader to read config files - >>> from py2store.slib.s_configparser import ConfigReader - >>> - >>> # from a (pretend) file - >>> from io import BytesIO, StringIO - >>> file_content_bytes = b''' - ... [Paths] - ... home_dir: /Users - ... my_dir: %(home_dir)s/lumberjack - ... my_pictures: %(my_dir)s/Pictures - ... - ... [Escape] - ... gain: 80%% # use a %% to escape the % sign (% is the only character that needs to be escaped)''' - >>> c = ConfigReader(file_content_bytes) # get configs from the bytes - >>> list(c) - ['DEFAULT', 'Paths', 'Escape'] - >>> ######## From a (pretend) file (pointer) ######## - >>> # Usually, you write your configs in a file and give ConfigReader the filepath, or open file pointer... - >>> pretend_file_pointer = StringIO(file_content_bytes.decode()) - >>> c = ConfigReader(pretend_file_pointer) - >>> list(c) - ['DEFAULT', 'Paths', 'Escape'] - >>> c['Paths'] # gives you a configparser.Section object - <Section: Paths> - >>> # A configparser.Section is a mapping. Let's see the keys - >>> list(c['Paths']) - ['home_dir', 'my_dir', 'my_pictures'] - >>> # here's a quick way to see both keys and values. Note how the home_dir interpolation was performed! - >>> dict(c['Paths']) - {'home_dir': '/Users', 'my_dir': '/Users/lumberjack', 'my_pictures': '/Users/lumberjack/Pictures'} - >>> - >>> ######## Get configs from a dict ######## - >>> config_dict = {'section1': {'key1': 'value1'}, - ... 'section2': {'keyA': 'valueA', 'keyB': 'valueB'}, - ... 'section3': {'foo': 'x', 'bar': 'y','baz': 'z'}} - >>> c = ConfigReader(config_dict) - >>> - >>> assert list(c) == ['DEFAULT', 'section1', 'section2', 'section3'] - >>> assert list(c['section3']) == ['foo', 'bar', 'baz'] - >>> - >>> ######## Get configs from a string ######## - >>> from py2store.slib.s_configparser import _test_config_str - >>> c = ConfigReader(_test_config_str, allow_no_value=True) - >>> list(c) - ['DEFAULT', 'Simple Values', 'All Values Are Strings', 'Multiline Values', 'No Values', 'You can use comments', 'Sections Can Be Indented'] - >>> list(c['Simple Values']) - ['key', 'spaces in keys', 'spaces in values', 'spaces around the delimiter', 'you can also use'] - """ - -
[docs] def persist(self): - raise NotImplementedError("persist disabled for ConfigReader")
- - def __setitem__(self, k, v): - raise NotImplementedError("__setitem__ disabled for ConfigReader") - - def __delitem__(self, k): - raise NotImplementedError("__delitem__ disabled for ConfigReader")
- - -# TODO: Need to wrap SectionProxy to make this work, since the obj and data here are -# those at a second level down. -# That is, when you do s['section']['key'] = obj, _data_of_obj gets activated on s, not s['section'] as desired -# class ConfigStoreWithLists(ConfigStore): -# def _data_of_obj(self, obj): -# if isinstance(obj, list): -# return '\n'.join(obj) -# else: -# return super()._data_of_obj(obj) -# -# def _obj_of_data(self, data): -# if data.startswith('\n'): -# return data.split('\n') -# else: -# return super()._obj_of_data(data) - - -from typing import Mapping, Iterable, Generator, Union -import re - - -# TODO: postprocess_ini_section_items and preprocess_ini_section_items: Add comma separated possibility? -# TODO: Find out if configparse has an option to do this processing alreadys -
[docs]def postprocess_ini_section_items(items: Union[Mapping, Iterable]) -> Generator: - r"""Transform newline-separated string values into actual list of strings (assuming that intent) - - >>> section_from_ini = { - ... 'name': 'aspyre', - ... 'keywords': '\n\tdocumentation\n\tpackaging\n\tpublishing' - ... } - >>> section_for_python = dict(postprocess_ini_section_items(section_from_ini)) - >>> section_for_python - {'name': 'aspyre', 'keywords': ['documentation', 'packaging', 'publishing']} - - """ - splitter_re = re.compile('[\n\r\t]+') - if isinstance(items, Mapping): - items = items.items() - for k, v in items: - if v.startswith('\n'): - v = splitter_re.split(v[1:]) - v = [vv.strip() for vv in v if vv.strip()] - yield k, v
- - -# TODO: Find out if configparse has an option to do this processing alreadys -
[docs]def preprocess_ini_section_items(items: Union[Mapping, Iterable]) -> Generator: - """Transform list values into newline-separated strings, in view of writing the value to a ini formatted section - >>> section = { - ... 'name': 'aspyre', - ... 'keywords': ['documentation', 'packaging', 'publishing'] - ... } - >>> for_ini = dict(preprocess_ini_section_items(section)) - >>> print('keywords =' + for_ini['keywords']) # doctest: +NORMALIZE_WHITESPACE - keywords = - documentation - packaging - publishing - - """ - if isinstance(items, Mapping): - items = items.items() - for k, v in items: - if isinstance(v, list): - v = '\n\t' + '\n\t'.join(v) - yield k, v
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/slib/s_zipfile.html b/docs/_modules/py2store/slib/s_zipfile.html deleted file mode 100644 index 9fd0530..0000000 --- a/docs/_modules/py2store/slib/s_zipfile.html +++ /dev/null @@ -1,761 +0,0 @@ - - - - - - - - py2store.slib.s_zipfile — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.slib.s_zipfile

-"""
-a data object layer for zipfile
-"""
-import inspect
-import os
-from functools import wraps
-from io import BytesIO
-from zipfile import (
-    ZipFile,
-    ZIP_STORED,
-    ZIP_DEFLATED,
-    ZIP_BZIP2,
-    ZIP_LZMA,
-)
-from py2store.base import KvReader, KvPersister
-from py2store.filesys import FileCollection
-from py2store.util import lazyprop, fullpath
-
-
-class COMPRESSION:
-    ZIP_STORED = (
-        ZIP_STORED  # The numeric constant for an uncompressed archive member.
-    )
-    ZIP_DEFLATED = ZIP_DEFLATED  # The numeric constant for the usual ZIP compression method. This requires zlib module.
-    ZIP_BZIP2 = ZIP_BZIP2  # The numeric constant for the BZIP2 compression method. This requires the bz2 module.
-    ZIP_LZMA = ZIP_LZMA  # The numeric constant for the LZMA compression method. This requires the lzma module.
-
-
-
[docs]def func_conjunction(func1, func2): - """Returns a function that is equivalent to lambda x: func1(x) and func2(x)""" - # Should assert that the input paramters of func1 and func2 are the same - assert ( - inspect.signature(func1).parameters - == inspect.signature(func2).parameters - ) - - @wraps(func2) - def func(*args, **kwargs): - return func1(*args, **kwargs) and func2(*args, **kwargs) - - return func
- - -def take_everything(fileinfo): - return True - - -
[docs]class ZipReader(KvReader): - r"""A KvReader to read the contents of a zip file. - Provides a KV perspective of https://docs.python.org/3/library/zipfile.html - - ``ZipReader`` has two value categories: Directories and Files. - Both categories are distinguishable by the keys, through the "ends with slash" convention. - - When a file, the value return is bytes, as usual. - - When a directory, the value returned is a ``ZipReader`` itself, with all params the same, except for the ``prefix`` - which serves `to specify the subfolder (that is, ``prefix`` acts as a filter). - - Note: If you get data zipped by a mac, you might get some junk along with it. - Namely `__MACOSX` folders `.DS_Store` files. I won't rant about it, since others have. - But you might find it useful to remove them from view. One choice is to use `py2store.trans.filt_iter` - to get a filtered view of the zips contents. In most cases, this should do the job: - ``` - # applied to store instance or class: - store = filt_iter(filt=lambda x: not x.startswith('__MACOSX') and '.DS_Store' not in x)(store) - ``` - - Another option is just to remove these from the zip file once and for all. In unix-like systems: - ``` - zip -d filename.zip __MACOSX/\* - zip -d filename.zip \*/.DS_Store - ``` - - Examples: - # >>> s = ZipReader('/path/to/some_zip_file.zip') - # >>> len(s) - # 53432 - # >>> list(s)[:3] # the first 3 elements (well... their keys) - # ['odir/', 'odir/app/', 'odir/app/data/'] - # >>> list(s)[-3:] # the last 3 elements (well... their keys) - # ['odir/app/data/audio/d/1574287049078391/m/Ctor.json', - # 'odir/app/data/audio/d/1574287049078391/m/intensity.json', - # 'odir/app/data/run/status.json'] - # >>> # getting a file (note that by default, you get bytes, so need to decode) - # >>> s['odir/app/data/run/status.json'].decode() - # b'{"test_phase_number": 9, "test_phase": "TestActions.IGNORE_TEST", "session_id": 0}' - # >>> # when you ask for the contents for a key that's a directory, - # >>> # you get a ZipReader filtered for that prefix: - # >>> s['odir/app/data/audio/'] - # ZipReader('/path/to/some_zip_file.zip', 'odir/app/data/audio/', {}, <function take_everything at 0x1538999e0>) - # >>> # Often, you only want files (not directories) - # >>> # You can filter directories out using the file_info_filt argument - # >>> s = ZipReader('/path/to/some_zip_file.zip', file_info_filt=ZipReader.FILES_ONLY) - # >>> len(s) # compare to the 53432 above, that contained dirs too - # 53280 - # >>> list(s)[:3] # first 3 keys are all files now - # ['odir/app/data/plc/d/1574304926795633/d/1574305026895702', - # 'odir/app/data/plc/d/1574304926795633/d/1574305276853053', - # 'odir/app/data/plc/d/1574304926795633/d/1574305159343326'] - # >>> - # >>> # ZipReader.FILES_ONLY and ZipReader.DIRS_ONLY are just convenience filt functions - # >>> # Really, you can provide any custom one yourself. - # >>> # This filter function should take a ZipInfo object, and return True or False. - # >>> # (https://docs.python.org/3/library/zipfile.html#zipfile.ZipInfo) - # >>> - # >>> import re - # >>> p = re.compile('audio.*\.json$') - # >>> my_filt_func = lambda fileinfo: bool(p.search(fileinfo.filename)) - # >>> s = ZipReader('/Users/twhalen/Downloads/2019_11_21.zip', file_info_filt=my_filt_func) - # >>> len(s) - # 48 - # >>> list(s)[:3] - # ['odir/app/data/audio/d/1574333557263758/m/Ctor.json', - # 'odir/app/data/audio/d/1574333557263758/m/intensity.json', - # 'odir/app/data/audio/d/1574288084739961/m/Ctor.json'] - """ - - def __init__( - self, zip_file, prefix='', open_kws=None, file_info_filt=None - ): - """ - - Args: - zip_file: A path to make ZipFile(zip_file) - prefix: A prefix to filter by. - open_kws: To be used when doing a ZipFile(...).open - file_info_filt: Filter for the FileInfo objects (see https://docs.python.org/3/library/zipfile.html) - of the paths listed in the zip file - """ - self.open_kws = open_kws or {} - self.file_info_filt = file_info_filt or ZipReader.EVERYTHING - self.prefix = prefix - if not isinstance(zip_file, ZipFile): - if isinstance(zip_file, str): - zip_file = fullpath(zip_file) - if isinstance(zip_file, dict): - zip_file = ZipFile(**zip_file) - elif isinstance(zip_file, (tuple, list)): - zip_file = ZipFile(*zip_file) - elif isinstance(zip_file, bytes): - zip_file = ZipFile(BytesIO(zip_file)) - else: - zip_file = ZipFile(zip_file) - self.zip_file = zip_file - - @classmethod - def for_files_only( - cls, zip_file, prefix='', open_kws=None, file_info_filt=None - ): - if file_info_filt is None: - file_info_filt = ZipReader.FILES_ONLY - else: - _file_info_filt = file_info_filt - - def file_info_filt(x): - return ZipReader.FILES_ONLY(x) and _file_info_filt(x) - - return cls(zip_file, prefix, open_kws, file_info_filt) - - @lazyprop - def info_for_key(self): - return { - x.filename: x - for x in self.zip_file.infolist() - if x.filename.startswith(self.prefix) and self.file_info_filt(x) - } - - def __iter__(self): - # using zip_file.infolist(), we could also filter for info (like directory/file) - yield from self.info_for_key.keys() - - def __getitem__(self, k): - if not self.info_for_key[k].is_dir(): - with self.zip_file.open(k, **self.open_kws) as fp: - return fp.read() - else: # is a directory - return self.__class__( - self.zip_file, k, self.open_kws, self.file_info_filt - ) - - def __len__(self): - return len(self.info_for_key) - - @staticmethod - def FILES_ONLY(fileinfo): - return not fileinfo.is_dir() - - @staticmethod - def DIRS_ONLY(fileinfo): - return fileinfo.is_dir() - - @staticmethod - def EVERYTHING(fileinfo): - return True - - def __repr__(self): - args_str = ', '.join( - ( - f"'{self.zip_file.filename}'", - f"'{self.prefix}'", - f'{self.open_kws}', - f'{self.file_info_filt}', - ) - ) - return f'{self.__class__.__name__}({args_str})'
- - -
[docs]class ZipFilesReader(FileCollection, KvReader): - """A local file reader whose keys are the zip filepaths of the rootdir and values are corresponding ZipReaders. - """ - - def __init__( - self, - rootdir, - subpath=r'.+\.zip', - pattern_for_field=None, - max_levels=0, - zip_reader=ZipReader, - **zip_reader_kwargs, - ): - super().__init__(rootdir, subpath, pattern_for_field, max_levels) - self.zip_reader = zip_reader - self.zip_reader_kwargs = zip_reader_kwargs - if self.zip_reader is ZipReader: - self.zip_reader_kwargs = dict( - dict( - prefix='', - open_kws=None, - file_info_filt=ZipReader.FILES_ONLY, - ), - **self.zip_reader_kwargs, - ) - - def __getitem__(self, k): - try: - return self.zip_reader(k, **self.zip_reader_kwargs) - except FileNotFoundError as e: - raise KeyError(f'FileNotFoundError: {e}')
- - -
[docs]class ZipFilesReaderAndBytesWriter(ZipFilesReader): - """Like ZipFilesReader, but the ability to write bytes (assumed to be valid bytes of the zip format) to a key - """ - - def __setitem__(self, k, v): - with open(k, 'wb') as fp: - fp.write(v)
- - -ZipFileReader = ZipFilesReader # back-compatibility alias - - -# TODO: Add easy connection to ExplicitKeymapReader and other path trans and cache useful for the folder of zips context -
[docs]class FlatZipFilesReader(ZipFilesReader): - """Read the union of the contents of multiple zip files. - A local file reader whose keys are the zip filepaths of the rootdir and values are corresponding ZipReaders. - - """ - - @lazyprop - def _zip_readers(self): - rootdir_len = len(self.rootdir) - return { - path[rootdir_len:]: super(FlatZipFilesReader, self).__getitem__( - path - ) - for path in super().__iter__() - } - - def __iter__(self): - for ( - zip_relpath, - zip_reader, - ) in self._zip_readers.items(): # go through the zip paths - for ( - path_in_zip - ) in ( - zip_reader - ): # go through the keys of the ZipReader (the zipped filepaths) - yield (zip_relpath, path_in_zip) - - def __getitem__(self, k): - ( - zip_relpath, - path_in_zip, - ) = k # k is a pair of the path to the zip file and the path of a file within it - return self._zip_readers[zip_relpath][path_in_zip]
- - -
[docs]def mk_flatzips_store( - dir_of_zips, - zip_pair_path_preproc=sorted, - mk_store=FlatZipFilesReader, - **extra_mk_store_kwargs, -): - """A store so that you can work with a folder that has a bunch of zip files, - as if they've all been extracted in the same folder. - Note that `zip_pair_path_preproc` can be used to control how to resolve key conflicts - (i.e. when you get two different zip files that have a same path in their contents). - The last path encountered by `zip_pair_path_preproc(zip_path_pairs)` is the one that will be used, so - one should make `zip_pair_path_preproc` act accordingly. - """ - from py2store.utils.explicit import ExplicitKeymapReader - - z = mk_store(dir_of_zips, **extra_mk_store_kwargs) - path_to_pair = {pair[1]: pair for pair in zip_pair_path_preproc(z)} - return ExplicitKeymapReader(z, id_of_key=path_to_pair)
- - -
[docs]class FilesOfZip(ZipReader): - def __init__(self, zip_file, prefix='', open_kws=None): - super().__init__( - zip_file, - prefix=prefix, - open_kws=open_kws, - file_info_filt=ZipReader.FILES_ONLY, - )
- - -# TODO: This file object item is more fundemental than file contents. Should it be at the base? -
[docs]class FileStreamsOfZip(FilesOfZip): - """Like FilesOfZip, but object returns are file streams instead. - So you use it like this: - - ``` - z = FileStreamsOfZip(rootdir) - with z[relpath] as fp: - ... # do stuff with fp, like fp.readlines() or such... - ``` - """ - - def __getitem__(self, k): - return self.zip_file.open(k, **self.open_kws)
- - -from py2store.paths import mk_relative_path_store -from py2store.util import partialclass - -ZipFileStreamsReader = mk_relative_path_store( - partialclass(ZipFilesReader, zip_reader=FileStreamsOfZip), - prefix_attr='rootdir', -) -ZipFileStreamsReader.__name__ = 'ZipFileStreamsReader' -ZipFileStreamsReader.__qualname__ = 'ZipFileStreamsReader' -ZipFileStreamsReader.__doc__ = ( - '''Like ZipFilesReader, but objects returned are file streams instead.''' -) - -from py2store.errors import OverWritesNotAllowedError - - -
[docs]class OverwriteNotAllowed(FileExistsError, OverWritesNotAllowedError): - ...
- - -
[docs]class EmptyZipError(KeyError, FileNotFoundError): - ...
- - -class _EmptyZipReader(KvReader): - def __init__(self, zip_filepath): - self.zip_filepath = zip_filepath - - def __iter__(self): - yield from () - - def infolist(self): - return [] - - def __getitem__(self, k): - raise EmptyZipError( - 'The store is empty: ZipStore(zip_filepath={self.zip_filepath})' - ) - - def open(self, *args, **kwargs): - raise EmptyZipError( - f"The zip file doesn't exist yet! Nothing was written in it: {self.zip_filepath}" - ) - # - # class OpenedNotExistingFile: - # zip_filepath = self.zip_filepath - # - # def read(self): - # raise EmptyZipError( - # f"The zip file doesn't exist yet! Nothing was written in it: {self.zip_filepath}") - # - # def __enter__(self, ): - # return self - # - # def __exit__(self, *exc): - # return False - # - # return OpenedNotExistingFile() - - -from zipfile import BadZipFile - -# TODO: Do all systems have this? If not, need to choose dflt carefully (choose dynamically?) -DFLT_COMPRESSION = COMPRESSION.ZIP_DEFLATED - - -# TODO: Revise ZipReader and ZipFilesReader architecture and make ZipStore be a subclass of Reader if poss -# TODO: What if I just want to zip a (single) file. What does py2store offer for that? -# TODO: How about set_obj (in misc.py)? Make it recognize the .zip extension and subextension (e.g. .txt.zip) serialize -
[docs]class ZipStore(KvPersister): - """Zip read and writing. - When you want to read zips, there's the `FilesOfZip`, `ZipReader`, or `ZipFilesReader` we know and love. - - Sometimes though, you want to write to zips too. For this, we have `ZipStore`. - - Since ZipStore can write to a zip, it's read functionality is not going to assume static data, - and cache things, as your favorite zip readers did. - This, and the acrobatics need to disguise the weird zipfile into something more... key-value natural, - makes for a not so efficient store, out of the box. - - I advise using one of the zip readers if all you need to do is read, or subclassing or - wrapping ZipStore with caching layers if it is appropriate to you. - - """ - - _zipfile_init_kw = dict( - compression=DFLT_COMPRESSION, - allowZip64=True, - compresslevel=None, - strict_timestamps=True, - ) - _open_kw = dict(pwd=None, force_zip64=False) - _writestr_kw = dict(compress_type=None, compresslevel=None) - zip_writer = None - - # @wraps(ZipReader.__init__) - def __init__( - self, - zip_filepath, - compression=DFLT_COMPRESSION, - allow_overwrites=True, - pwd=None, - ): - self.zip_filepath = fullpath(zip_filepath) - self.zip_filepath = zip_filepath - self.zip_writer_opened = False - self.allow_overwrites = allow_overwrites - self._zipfile_init_kw = dict( - self._zipfile_init_kw, compression=compression - ) - self._open_kw = dict(self._open_kw, pwd=pwd) - - @staticmethod - def files_only_filt(fileinfo): - return not fileinfo.is_dir() - - @property - def zip_reader(self): - if os.path.isfile(self.zip_filepath): - return ZipFile( - self.zip_filepath, mode='r', **self._zipfile_init_kw - ) - else: - return _EmptyZipReader(self.zip_filepath) - - def __iter__(self): - # using zip_file.infolist(), we could also filter for info (like directory/file) - yield from ( - fi.filename - for fi in self.zip_reader.infolist() - if self.files_only_filt(fi) - ) - - def __getitem__(self, k): - with self.zip_reader.open(k, **dict(self._open_kw, mode='r')) as fp: - return fp.read() - - def __repr__(self): - args_str = ', '.join( - ( - f"'{self.zip_filepath}'", - f"'allow_overwrites={self.allow_overwrites}'", - ) - ) - return f'{self.__class__.__name__}({args_str})' - - def __contains__(self, k): - try: - with self.zip_reader.open( - k, **dict(self._open_kw, mode='r') - ) as fp: - pass - return True - except ( - KeyError, - BadZipFile, - ): # BadZipFile is to catch when zip file exists, but is empty. - return False - - # # TODO: Find better way to avoid duplicate keys! - # # TODO: What's the right Error to raise - # def _assert_non_existing_key(self, k): - # # if self.zip_writer is not None: - # if not self.zip_writer_opened: - # try: - # self.zip_reader.open(k) - # raise OverwriteNotAllowed(f"You're not allowed to overwrite an existing key: {k}") - # except KeyError as e: - # if isinstance(e, EmptyZipError) or e.args[-1].endswith('archive'): - # pass # - # else: - # raise OverwriteNotAllowed(f"You're not allowed to overwrite an existing key: {k}") - - # TODO: Repeated with zip_writer logic. Consider DRY possibilities. - def __setitem__(self, k, v): - if k in self: - if self.allow_overwrites and not self.zip_writer_opened: - del self[k] # remove key so it can be overwritten - else: - if self.zip_writer_opened: - raise OverwriteNotAllowed( - f"When using the context mode, you're not allowed to overwrite an existing key: {k}" - ) - else: - raise OverwriteNotAllowed( - f"You're not allowed to overwrite an existing key: {k}" - ) - - if self.zip_writer_opened: - with self.zip_writer.open( - k, **dict(self._open_kw, mode='w') - ) as fp: - return fp.write(v) - else: - with ZipFile( - self.zip_filepath, mode='a', **self._zipfile_init_kw - ) as zip_writer: - with zip_writer.open(k, **dict(self._open_kw, mode='w')) as fp: - return fp.write(v) - - def __delitem__(self, k): - try: - os.system(f'zip -d {self.zip_filepath} {k}') - except Exception as e: - raise KeyError(f'{e.__class__}: {e.args}') - # raise NotImplementedError("zipfile, the backend of ZipStore, doesn't support deletion, so neither will we.") - - def open(self): - self.zip_writer = ZipFile( - self.zip_filepath, mode='a', **self._zipfile_init_kw - ) - self.zip_writer_opened = True - return self - - def close(self): - if self.zip_writer is not None: - self.zip_writer.close() - self.zip_writer_opened = False - - __enter__ = open - - def __exit__(self, *exc): - self.close() - return False
- - -# TODO: The way prefix and file_info_filt is handled is not efficient -# TODO: prefix is silly: less general than filename_filt would be, and not even producing relative paths -# (especially when getitem returns subdirs) - - -# trans alternative: -# from py2store.trans import mk_kv_reader_from_kv_collection, wrap_kvs -# -# ZipFileReader = wrap_kvs(mk_kv_reader_from_kv_collection(FileCollection, name='_ZipFileReader'), -# name='ZipFileReader', -# obj_of_data=ZipReader) - -# -# if __name__ == '__main__': -# from py2store.test.simple import test_local_file_ops -# -# test_local_file_ops() -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/sources.html b/docs/_modules/py2store/sources.html deleted file mode 100644 index c88637f..0000000 --- a/docs/_modules/py2store/sources.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - - - py2store.sources — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.sources

-from typing import Mapping, Optional
-from inspect import getsource
-
-# from py2store.util import lazyprop, num_of_args
-from py2store import KvReader, KvPersister, cached_keys
-from py2store.util import copy_attrs
-from py2store.utils.signatures import Sig
-
-
-
[docs]class FuncReader(KvReader): - """Reader that seeds itself from a data fetching function list - Uses the function list names as the keys, and their returned value as the values. - - For example: You have a list of urls that contain the data you want to have access to. - You can write functions that bare the names you want to give to each dataset, and have the function - fetch the data from the url, extract the data from the response and possibly prepare it - (we advise minimally, since you can always transform from the raw source, but the opposite can be impossible). - - >>> def foo(): - ... return 'bar' - >>> def pi(): - ... return 3.14159 - >>> s = FuncReader([foo, pi]) - >>> list(s) - ['foo', 'pi'] - >>> s['foo'] - 'bar' - >>> s['pi'] - 3.14159 - """ - - def __init__(self, funcs): - # TODO: assert no free arguments (arguments are allowed but must all have defaults) - self.funcs = funcs - self._func = {func.__name__: func for func in funcs} - - def __contains__(self, k): - return k in self._func - - def __iter__(self): - yield from self._func - - def __len__(self): - return len(self._func) - - def __getitem__(self, k): - return self._func[k]() # call the func
- - -
[docs]class FuncDag(FuncReader): - def __init__(self, funcs, **kwargs): - super().__init__(funcs) - self._sig = {fname: Sig(func) for fname, func in self._func.items()} - # self._input_names = sum(self._sig) - - def __getitem__(self, k): - return self._func_of_name[k]() # call the func
- - -import os - -psep = os.path.sep - -ddir = lambda o: [x for x in dir(o) if not x.startswith("_")] - - -def not_underscore_prefixed(x): - return not x.startswith("_") - - -def _path_to_module_str(path, root_path): - assert path.endswith(".py") - path = path[:-3] - if root_path.endswith(psep): - root_path = root_path[:-1] - root_path = os.path.dirname(root_path) - len_root = len(root_path) + 1 - path_parts = path[len_root:].split(psep) - if path_parts[-1] == "__init__.py": - path_parts = path_parts[:-1] - return ".".join(path_parts) - - -
[docs]class ObjReader(KvReader): - def __init__(self, obj): - self.src = obj - copy_attrs( - target=self, - source=self.src, - attrs=("__name__", "__qualname__", "__module__"), - raise_error_if_an_attr_is_missing=False - ) - - def __repr__(self): - return f"{self.__class__.__qualname__}({self.src})" - - @property - def _source(self): - from warnings import warn - - warn("Deprecated: Use .src instead of ._source", DeprecationWarning, 2) - return self.src
- - -# class SourceReader(KvReader): -# def __getitem__(self, k): -# return getsource(k) - -# class NestedObjReader(ObjReader): -# def __init__(self, obj, src_to_key, key_filt=None, ): - -# Pattern: -
[docs]@cached_keys(keys_cache=set, name="Attrs") -class Attrs(ObjReader): - def __init__(self, obj, key_filt=not_underscore_prefixed): - super().__init__(obj) - self._key_filt = key_filt - - @classmethod - def module_from_path( - cls, path, key_filt=not_underscore_prefixed, name=None, root_path=None - ): - import importlib.util - - if name is None: - if root_path is not None: - try: - name = _path_to_module_str(path, root_path) - except Exception: - name = "fake.module.name" - spec = importlib.util.spec_from_file_location(name, path) - foo = importlib.util.module_from_spec(spec) - spec.loader.exec_module(foo) - return cls(foo, key_filt) - - def __iter__(self): - yield from filter(self._key_filt, dir(self.src)) - - def __getitem__(self, k): - return self.__class__(getattr(self.src, k)) - - def __repr__(self): - return f"{self.__class__.__qualname__}({self.src}, {self._key_filt})"
- - -Ddir = Attrs # for back-compatibility, temporarily - - -# TODO: Make it work with a store, without having to load and store the values explicitly. -
[docs]class DictAttr(KvPersister): - """Convenience class to hold Key-Val pairs with both a dict-like and struct-like interface. - The dict-like interface has just the basic get/set/del/iter/len - (all "dunders": none visible as methods). There is no get, update, etc. - This is on purpose, so that the only visible attributes (those you get by tab-completion for instance) - are the those you injected. - - >>> da = DictAttr(foo='bar', life=42) - >>> da.foo - 'bar' - >>> da['life'] - 42 - >>> da.true = 'love' - >>> len(da) # count the number of fields - 3 - >>> da['friends'] = 'forever' # write as dict - >>> da.friends # read as attribute - 'forever' - >>> list(da) # list fields (i.e. keys i.e. attributes) - ['foo', 'life', 'true', 'friends'] - >>> list(da.items()) - [('foo', 'bar'), ('life', 42), ('true', 'love'), ('friends', 'forever')] - >>> del da['friends'] # delete as dict - >>> del da.foo # delete as attribute - >>> list(da) - ['life', 'true'] - >>> da._source # the hidden dict that is wrapped - {'life': 42, 'true': 'love'} - """ - - _source = None - - def __init__(self, _source: Optional[Mapping] = None, **keys_and_values): - if _source is not None: - assert isinstance(_source, Mapping) - self._source = _source - else: - super().__setattr__("_source", {}) - for k, v in keys_and_values.items(): - setattr(self, k, v) - - def __getitem__(self, k): - return self._source[k] - - def __setitem__(self, k, v): - setattr(self, k, v) - - def __delitem__(self, k): - delattr(self, k) - - def __iter__(self): - return iter(self._source.keys()) - - def __len__(self): - return len(self._source) - - def __setattr__(self, k, v): - self._source[k] = v - super().__setattr__(k, v) - - def __delattr__(self, k): - del self._source[k] - super().__delattr__(k)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/stores/dropbox_store.html b/docs/_modules/py2store/stores/dropbox_store.html deleted file mode 100644 index e94140d..0000000 --- a/docs/_modules/py2store/stores/dropbox_store.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - py2store.stores.dropbox_store — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.stores.dropbox_store

-from functools import wraps
-
-from py2store.base import Store
-from py2store.key_mappers.paths import PrefixRelativizationMixin
-from py2store.persisters.dropbox_w_dropbox import DropboxPersister
-
-
-
[docs]class DropboxBinaryStore(PrefixRelativizationMixin, Store, DropboxPersister): - @wraps(DropboxPersister.__init__) - def __init__(self, *args, **kwargs): - super().__init__(store=DropboxPersister(*args, **kwargs)) - self._prefix = self.store._prefix
- - -
[docs]class DropboxTextStore(DropboxBinaryStore): - def _obj_of_data(self, data): - return data.decode() - - def _data_of_obj(self, obj): - return obj.encode()
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/stores/local_store.html b/docs/_modules/py2store/stores/local_store.html deleted file mode 100644 index 2f37208..0000000 --- a/docs/_modules/py2store/stores/local_store.html +++ /dev/null @@ -1,561 +0,0 @@ - - - - - - - - py2store.stores.local_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.stores.local_store

-"""
-stores to operate on local files
-"""
-import os
-from functools import wraps
-
-from py2store.base import Store, Persister
-from py2store.core import PrefixRelativizationMixin, PrefixRelativization
-from py2store.paths import mk_relative_path_store
-from py2store.serializers.pickled import mk_pickle_rw_funcs
-from py2store.persisters.local_files import (
-    PathFormatPersister,
-    DirpathFormatKeys,
-    DirReader,
-    ensure_slash_suffix,
-)
-
-# from py2store.filesys import DirCollection
-from py2store.mixins import SimpleJsonMixin
-
-
-
[docs]class PathFormatStore(PathFormatPersister, Persister): - """ - Local file store using templated relative paths. - - >>> from tempfile import gettempdir - >>> import os - >>> - >>> def write_to_key(fullpath_of_relative_path, relative_path, content): # a function to write content in files - ... with open(fullpath_of_relative_path(relative_path), 'w') as fp: - ... fp.write(content) - >>> - >>> # Preparation: Make a temporary rootdir and write two files in it - >>> rootdir = os.path.join(gettempdir(), 'path_format_store_test' + os.sep) - >>> if not os.path.isdir(rootdir): - ... os.mkdir(rootdir) - >>> # recreate directory (remove existing files, delete directory, and re-create it) - >>> for f in os.listdir(rootdir): - ... fullpath = os.path.join(rootdir, f) - ... if os.path.isfile(fullpath): - ... os.remove(os.path.join(rootdir, f)) - >>> if os.path.isdir(rootdir): - ... os.rmdir(rootdir) - >>> if not os.path.isdir(rootdir): - ... os.mkdir(rootdir) - >>> - >>> filepath_of = lambda p: os.path.join(rootdir, p) # a function to get a fullpath from a relative one - >>> # and make two files in this new dir, with some content - >>> write_to_key(filepath_of, 'a', 'foo') - >>> write_to_key(filepath_of, 'b', 'bar') - >>> - >>> # point the obj source to the rootdir - >>> s = PathFormatStore(path_format=rootdir) - >>> - >>> # assert things... - >>> assert s._prefix == rootdir # the _rootdir is the one given in constructor - >>> assert s[filepath_of('a')] == 'foo' # (the filepath for) 'a' contains 'foo' - >>> - >>> # two files under rootdir (as long as the OS didn't create it's own under the hood) - >>> len(s) - 2 - >>> assert list(s) == [filepath_of('a'), filepath_of('b')] # there's two files in s - >>> filepath_of('a') in s # rootdir/a is in s - True - >>> filepath_of('not_there') in s # rootdir/not_there is not in s - False - >>> filepath_of('not_there') not in s # rootdir/not_there is not in s - True - >>> assert list(s.keys()) == [filepath_of('a'), filepath_of('b')] # the keys (filepaths) of s - >>> sorted(list(s.values())) # the values of s (contents of files) - ['bar', 'foo'] - >>> assert list(s.items()) == [(filepath_of('a'), 'foo'), (filepath_of('b'), 'bar')] # the (path, content) items - >>> assert s.get('this key is not there', None) is None # trying to get the val of a non-existing key returns None - >>> s.get('this key is not there', 'some default value') # ... or whatever you say - 'some default value' - >>> - >>> # add more files to the same folder - >>> write_to_key(filepath_of, 'this.txt', 'this') - >>> write_to_key(filepath_of, 'that.txt', 'blah') - >>> write_to_key(filepath_of, 'the_other.txt', 'bloo') - >>> # see that you now have 5 files - >>> len(s) - 5 - >>> # and these files contain values: - >>> sorted(s.values()) - ['bar', 'blah', 'bloo', 'foo', 'this'] - >>> - >>> # but if we make an obj source to only take files whose extension is '.txt'... - >>> s = PathFormatStore(path_format=rootdir + '{}.txt') - >>> - >>> rootdir_2 = os.path.join(gettempdir(), 'obj_source_test_2') # get another rootdir - >>> if not os.path.isdir(rootdir_2): - ... os.mkdir(rootdir_2) - >>> filepath_of_2 = lambda p: os.path.join(rootdir_2, p) - >>> # and make two files in this new dir, with some content - >>> write_to_key(filepath_of, 'this.txt', 'this') - >>> write_to_key(filepath_of, 'that.txt', 'blah') - >>> write_to_key(filepath_of, 'the_other.txt', 'bloo') - >>> - >>> ss = PathFormatStore(path_format=rootdir_2 + '{}.txt') - >>> - >>> assert s != ss # though pointing to identical content, o and oo are not equal since the paths are not equal! - """ - - pass
- - -RelPathLocalFileStore = mk_relative_path_store( - PathFormatPersister, __name__='RelPathLocalFileStore' -) -RelPathLocalFileStore.__doc__ = ( - '''Local file store using templated relative paths.''' -) - -RelPathLocalFileStoreEnforcingFormat = mk_relative_path_store( - PathFormatPersister, __name__='RelPathLocalFileStoreEnforcingFormat' -) -RelPathLocalFileStoreEnforcingFormat.__doc__ = '''A RelativePathFormatStore, but that won't allow one to use a key that is not valid - (according to the self.store.is_valid_key boolean method)''' - -# aliases for back compatibility -RelativePathFormatStore = RelPathLocalFileStore -RelativePathFormatStoreEnforcingFormat = RelPathLocalFileStoreEnforcingFormat - - -# Old version it replaces -# class RelativePathFormatStore(PrefixRelativizationMixin, Store): -# """Local file store using templated relative paths. -# """ -# -# @wraps(PathFormatStore.__init__) -# def __init__(self, *args, **kwargs): -# super().__init__(store=PathFormatStore(*args, **kwargs)) -# self._prefix = self.store._prefix -# -# -# class RelativePathFormatStoreEnforcingFormat(RelativePathFormatStore): -# """A RelativePathFormatStore, but that won't allow one to use a key that is not valid -# (according to the self.store.is_valid_key boolean method). -# """ -# -# def _id_of_key(self, k): -# _id = super()._id_of_key(k) -# if self.store.is_valid_key(_id): -# return _id -# else: -# raise KeyError(f"Key not valid: {k}") - - -
[docs]class MakeMissingDirsStoreMixin: - """Will make a local file store automatically create the directories needed to create a file. - Should be placed before the concrete perisister in the mro but in such a manner so that it receives full paths. - """ - - def __setitem__(self, k, v): - _id = self._id_of_key(k) - dirname = os.path.dirname(_id) - os.makedirs(dirname, exist_ok=1) - super().__setitem__(k, v)
- - -
[docs]class PathFormatStoreWithPrefix(Store): - @wraps(PathFormatStore.__init__) - def __init__(self, *args, **kwargs): - super().__init__(store=PathFormatStore(*args, **kwargs)) - self._prefix = self.store._prefix
- - -# Would like to replace the above pattern with what's below, but -# from py2store.trans import store_wrap -# PathFormatStoreWithPrefix = store_wrap(PathFormatStore, 'PathFormatStoreWithPrefix') - - -
[docs]class RelativePathFormatStore2( - PrefixRelativizationMixin, PathFormatStoreWithPrefix -): - pass
- - -
[docs]class LocalTextStore(RelativePathFormatStore): - """Local files store for text data""" - - def __init__(self, path_format, max_levels=None): - super().__init__(path_format, max_levels=max_levels, mode='t')
- - -
[docs]class LocalBinaryStore(RelativePathFormatStore): - """Local files store for binary data""" - - def __init__(self, path_format, max_levels=None): - super().__init__(path_format, max_levels=max_levels, mode='b')
- - -
[docs]class LocalPickleStore(RelativePathFormatStore): - """Local files store with pickle serialization""" - - def __init__( - self, - path_format, - max_levels=None, - fix_imports=True, - protocol=None, - pickle_encoding='ASCII', - pickle_errors='strict', - **open_kwargs, - ): - super().__init__( - path_format, max_levels=max_levels, mode='b', **open_kwargs - ) - self._loads, self._dumps = mk_pickle_rw_funcs( - fix_imports, protocol, pickle_encoding, pickle_errors - ) - - @classmethod - def for_dill( - cls, path_format, max_levels=None, open_kwargs=None, *args, **kwargs - ): - from py2store.serializers.pickled import mk_dill_rw_funcs - - open_kwargs = open_kwargs or {} - self = cls(path_format, max_levels=max_levels, **open_kwargs) - self._loads, self._dumps = mk_dill_rw_funcs(*args, **kwargs) - return self - - def __getitem__(self, k): - try: - return self._loads(super().__getitem__(k)) - except (ModuleNotFoundError, AttributeError) as e: - if isinstance(e, AttributeError) and 'module' not in str(e): - raise - else: - raise type(e)(f'Some modules are missing to unpickle {k}: {e}') - - def __setitem__(self, k, v): - return super().__setitem__(k, self._dumps(v)) - - # TODO: hack to take care of problem with head not playing well with wrappers. Find better solution. - def head(self): - for k, v in self.items(): - return k, v
- - -
[docs]class LocalJsonStore(SimpleJsonMixin, LocalTextStore): - __doc__ = str(LocalTextStore.__doc__) + SimpleJsonMixin._docsuffix
- - -PickleStore = LocalPickleStore # alias - - -def mk_tmp_quick_store_dirpath(dirname=''): - from tempfile import gettempdir - - temp_root = gettempdir() - return os.path.join(temp_root, dirname) - - -def mk_absolute_path(path_format): - if path_format.startswith('~'): - path_format = os.path.expanduser(path_format) - elif path_format.startswith('.'): - path_format = os.path.abspath(path_format) - return path_format - - -
[docs]class AutoMkDirsOnSetitemMixin: - """A mixin that will automatically create directories on setitem, when missing.""" - - def __setitem__(self, k, v): - dirname = os.path.dirname(os.path.join(self._prefix, k)) - os.makedirs(dirname, exist_ok=True) - return super().__setitem__(k, v)
- - -
[docs]class AutoMkPathformatMixin: - """A mixin that will choose a path_format if none given - """ - - _tmp_dirname = 'quick_store' - _docsuffix = ' with default temp root and auto dir generation on write.' - - @classmethod - def mk_tmp_quick_store_path_format(cls, subpath=''): - return mk_tmp_quick_store_dirpath( - os.path.join(cls._tmp_dirname, subpath) - ) - - def __init__(self, path_format=None, max_levels=None): - if path_format is None: - path_format = self.mk_tmp_quick_store_path_format() - print( - f'No path_format was given, so taking one from a tmp dir. Namely:\n\t{path_format}' - ) - else: - path_format = mk_absolute_path(path_format) - super().__init__(path_format, max_levels=max_levels)
- - -
[docs]class QuickLocalStoreMixin(AutoMkPathformatMixin, AutoMkDirsOnSetitemMixin): - """A mixin that will choose a path_format if none given, - and will automatically create directories on setitem, when missing. - """
- - # _tmp_dirname = "quick_store" - # _docsuffix = " with default temp root and auto dir generation on write." - # - # @classmethod - # def mk_tmp_quick_store_path_format(cls, subpath=""): - # return mk_tmp_quick_store_dirpath( - # os.path.join(cls._tmp_dirname, subpath) - # ) - # - # def __init__(self, path_format=None, max_levels=None): - # if path_format is None: - # path_format = self.mk_tmp_quick_store_path_format() - # print( - # f"No path_format was given, so taking one from a tmp dir. Namely:\n\t{path_format}" - # ) - # else: - # path_format = mk_absolute_path(path_format) - # super().__init__(path_format, max_levels=max_levels) - # - # def __setitem__(self, k, v): - # dirname = os.path.dirname(os.path.join(self._prefix, k)) - # os.makedirs(dirname, exist_ok=True) - # return super().__setitem__(k, v) - - -
[docs]class QuickTextStore(QuickLocalStoreMixin, LocalTextStore): - __doc__ = str(LocalTextStore.__doc__) + QuickLocalStoreMixin._docsuffix
- - -
[docs]class QuickBinaryStore(QuickLocalStoreMixin, LocalBinaryStore): - __doc__ = str(LocalBinaryStore.__doc__) + QuickLocalStoreMixin._docsuffix
- - -
[docs]class QuickJsonStore(SimpleJsonMixin, QuickTextStore): - __doc__ = str(QuickTextStore.__doc__) + SimpleJsonMixin._docsuffix
- - -
[docs]class QuickPickleStore(QuickLocalStoreMixin, PickleStore): - __doc__ = str(PickleStore.__doc__) + QuickLocalStoreMixin._docsuffix
- - -QuickStore = QuickPickleStore # alias -LocalStore = QuickStore # alias - - -
[docs]class DirStore(Store): - """A store for local directories. - Keys are directory names and values are subdirectory DirStores. - - >>> from py2store import __file__ - >>> import os - >>> root = os.path.dirname(__file__) - >>> s = DirStore(root) - >>> assert set(s).issuperset({'stores', 'persisters', 'serializers', 'key_mappers'}) - """ - - def __init__(self, rootdir): - rootdir = ensure_slash_suffix(rootdir) - super().__init__(store=DirReader(rootdir)) - self._prefix = rootdir - - key_wrap = PrefixRelativization(_prefix=rootdir) - os_sep = os.sep - self._id_of_key = lambda k: key_wrap._id_of_key(k) + os_sep - self._key_of_id = lambda k: key_wrap._key_of_id(k)[:-1] - - # TODO: Look into alternatives for the raison d'etre of _new_node and _class_name - # (They are there, because using self.__class__ directly goes to super) - self.store._new_node = self.__class__ - self.store._class_name = self.__class__.__name__
- - -
[docs]class RelativeDirPathFormatKeys(PrefixRelativizationMixin, Store): - @wraps(DirpathFormatKeys.__init__) - def __init__(self, *args, **kwargs): - super().__init__(store=DirpathFormatKeys(*args, **kwargs)) - self._prefix = self.store._prefix
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/stores/mongo_store.html b/docs/_modules/py2store/stores/mongo_store.html deleted file mode 100644 index 42d29b3..0000000 --- a/docs/_modules/py2store/stores/mongo_store.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - - - py2store.stores.mongo_store — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.stores.mongo_store

-from functools import wraps
-
-from py2store.persisters.mongo_w_pymongo import OldMongoPersister
-
-from py2store.base import Store
-from py2store.util import lazyprop
-
-
-
[docs]class MongoStore(Store): - @wraps(OldMongoPersister.__init__) - def __init__(self, *args, **kwargs): - persister = OldMongoPersister(*args, **kwargs) - super().__init__(persister)
- - -
[docs]class MongoTupleKeyStore(MongoStore): - """ - MongoStore using tuple keys. - - >>> s = MongoTupleKeyStore(db_name='py2store_tests', collection_name='tmp', key_fields=('_id', 'user')) - >>> for k in s: del s[k] - >>> k = (1234, 'user') - >>> v = {'name': 'bob', 'age': 42} - >>> if k in s: # deleting all docs in tmp - ... del s[k] - >>> assert (k in s) == False # see that key is not in store (and testing __contains__) - >>> orig_length = len(s) - >>> s[k] = v - >>> assert len(s) == orig_length + 1 - >>> assert k in list(s) - >>> assert s[k] == v - >>> assert s.get(k) == v - >>> assert v in list(s.values()) - >>> assert (k in s) == True # testing __contains__ again - >>> del s[k] - >>> assert len(s) == orig_length - """ - - @lazyprop - def _key_fields(self): - return self.store._key_fields - - def _id_of_key(self, k): - return { - field: field_val for field, field_val in zip(self._key_fields, k) - } - - def _key_of_id(self, _id): - return tuple(_id[x] for x in self._key_fields)
- - -# TODO: Finish -
[docs]class MongoAnyKeyStore(MongoStore): - """ - MongoStore using tuple keys. - - >>> s = MongoAnyKeyStore(db_name='py2store_tests', collection_name='tmp', ) - >>> for k in s: del s[k] - >>> s['foo'] = {'must': 'be', 'a': 'dict'} - >>> s['foo'] - {'must': 'be', 'a': 'dict'} - """ - - @wraps(MongoStore.__init__) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - assert isinstance( - self._key_fields, tuple - ), "key_fields should be a tuple or a string" - assert ( - len(self._key_fields) == 1 - ), "key_fields must have one and only one element (a string)" - self._key_field = self._key_fields[0] - - @lazyprop - def _key_fields(self): - return self.store._key_fields - - def _id_of_key(self, k): - return {self._key_field: k} - - def _key_of_id(self, _id): - return _id[self._key_field] - - def __setitem__(self, k, v): - if k in self: - del self[k] - super().__setitem__(k, v)
- - -def test_mongo_store(s=MongoStore(), k=None, v=None): - if k is None: - k = {"_id": "foo"} - if v is None: - v = {"val": "bar"} - if k in s: # deleting all docs in tmp - del s[k] - assert ( - k in s - ) == False # see that key is not in store (and testing __contains__) - orig_length = len(s) - s[k] = v - assert len(s) == orig_length + 1 - assert k in list(s) - assert s[k] == v - assert s.get(k) == v - assert v in list(s.values()) - assert (k in s) == True # testing __contains__ again - del s[k] - assert len(s) == 0 -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/stores/s3_store.html b/docs/_modules/py2store/stores/s3_store.html deleted file mode 100644 index f0bd50d..0000000 --- a/docs/_modules/py2store/stores/s3_store.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - - - py2store.stores.s3_store — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.stores.s3_store

-import pickle
-from py2store.base import Store
-from py2store.util import ModuleNotFoundErrorNiceMessage
-from py2store.persisters.s3_w_boto3 import S3BucketPersister
-from py2store.key_mappers.paths import mk_relative_path_store
-
-with ModuleNotFoundErrorNiceMessage():
-    from botocore.client import Config
-
-DFLT_AWS_S3_ENDPOINT = "https://s3.amazonaws.com"
-DFLT_BOTO_CLIENT_VERIFY = None
-DFLT_SIGNATURE_VERSION = "s3v4"
-DFLT_CONFIG = Config(signature_version=DFLT_SIGNATURE_VERSION)
-
-
-
[docs]class S3AbsPathBinaryStore(Store): - # @wraps(S3BucketPersister.from_s3_resource_kwargs) - def __init__(self, bucket_name, _prefix: str = "", resource_kwargs=None): - persister = S3BucketPersister.from_s3_resource_kwargs( - bucket_name, _prefix, resource_kwargs - ) - super().__init__(persister) - self._prefix = self.store._prefix - - def _id_of_key(self, k): - return self.store._source.Object(key=k) - - def _key_of_id(self, _id): - return _id.key
- - -S3BinaryStore = mk_relative_path_store( - S3AbsPathBinaryStore, - __name__="S3BinaryStore", - __module__=__name__, -) - - -
[docs]class S3TextStore(S3BinaryStore): - def _obj_of_data(self, data): - return data.decode()
- - -S3StringStore = S3TextStore - - -
[docs]class S3PickleStore(S3BinaryStore): - def _obj_of_data(self, data): - return pickle.loads(data) - - def _data_of_obj(self, obj): - return pickle.dumps(obj)
- -# def get_s3_resource(aws_access_key_id, -# aws_secret_access_key, -# endpoint_url=DFLT_AWS_S3_ENDPOINT, -# verify=DFLT_BOTO_CLIENT_VERIFY, -# config=DFLT_CONFIG): -# """ -# Get boto3 s3 resource. -# :param aws_access_key_id: -# :param aws_secret_access_key: -# :param endpoint_url: -# :param verify: -# :param signature_version: -# :return: -# """ -# return boto3.resource('s3', -# endpoint_url=endpoint_url, -# aws_access_key_id=aws_access_key_id, -# aws_secret_access_key=aws_secret_access_key, -# verify=verify, -# config=config) -# -# -# def get_s3_bucket(name, -# aws_access_key_id, -# aws_secret_access_key, -# endpoint_url=DFLT_AWS_S3_ENDPOINT, -# verify=DFLT_BOTO_CLIENT_VERIFY, -# config=DFLT_CONFIG): -# s3 = get_s3_resource(endpoint_url=endpoint_url, -# aws_access_key_id=aws_access_key_id, -# aws_secret_access_key=aws_secret_access_key, -# verify=verify, -# config=config) -# return s3.Bucket(name) - - -# class S3BucketCollection(IterBasedSizedMixin): -# """ -# A S3BucketDacc collection. -# A collection is a iterable and sizable container. -# That is, this mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)) to S3BucketDacc. -# -# Note: Subclasses IterBasedSizedMixin for the sole purpose of reusing it's __len__ method before any KV wrapping -# """ -# -# def __iter__(self): -# return iter(self._s3_bucket.objects.filter(Prefix=self._prefix)) -# -# def __contains__(self, k): -# """ -# Check if key exists -# :param k: A key to search for -# :return: True if k exists, False if not -# """ -# # TODO: s3_client.head_object(Bucket=dacc.bucket_name, Key=k) slightly more efficient but needs boto3.client -# try: -# self._id_of_key(k).load() -# return True # if all went well -# except ClientError as e: -# if e.response['Error']['Code'] == "404": -# # The object does not exist. -# return False -# else: -# # Something else has gone wrong. -# raise -# -# def _id_of_key(self, k): -# return self._s3_bucket.Object(key=k) -# -# def _key_of_id(self, _id): -# return _id.key -# -# -# class S3BucketReaderMixin: -# """ Mixin to add read functionality to a S3BucketDacc.""" -# -# def __getitem__(self, k): -# try: # TODO: Didn't manage to catch this exception for some reason. Make it work! -# return k.get()['Body'].read() -# except Exception as e: -# if hasattr(e, '__name__'): -# if e.__name__ == 'NoSuchKey': -# raise NoSuchKeyError("Key wasn't found: {}".format(k)) -# raise # if you got so far -# -# -# class S3BucketWriterMixin: -# """ A mixin to add write and delete functionality """ -# -# def __setitem__(self, k, v): -# """ -# Write data to s3 key. -# Method will check if key is valid before writing data to it, -# but will not check if data is already stored there. -# This means that any data previously stored at the key's location will be lost. -# :param k: s3 key -# :param v: data to write -# :return: None -# """ -# # TODO: Faster to ignore s3 response, but perhaps better to get it, possibly cache it, and possibly handle it -# k.put(Body=v) -# -# -# class S3BucketDeleterMixin: -# def __delitem__(self, k): -# """ -# Delete data stored at key k. -# Method will check if key is valid before deleting its data. -# :param k: -# :return: -# """ -# # TODO: Faster to ignore s3 response, but perhaps better to get it, possibly cache it, and possibly handle it -# try: # TODO: Didn't manage to catch this exception for some reason. Make it work! -# k.delete() -# except Exception as e: -# if hasattr(e, '__name__'): -# if e.__name__ == 'NoSuchKey': -# raise NoSuchKeyError("Key wasn't found: {}".format(k)) -# raise # if you got so far -# -# -# class S3BucketRWD(S3BucketReaderMixin, S3BucketWriterMixin, S3BucketDeleterMixin): -# def __init__(self, bucket_name: str, _s3_bucket, _prefix: str = ''): -# """ -# S3 Bucket accessor. -# This class is meant to be subclassed, used with other mixins that actually add read and write methods. -# All S3BucketDacc does is create (or maintain) a bucket object, offer validation (is_valid) -# and assertion methods (assert_is_valid) methods to check that a key is prefixed by given _prefix, and -# more importantly, offers a hidden _id_of_key method that returns an object for a given key. -# -# Observe that the _s3_bucket constructor argument is a boto3 s3.Bucket, but offers other factories to make -# a S3BucketDacc instance. -# For example. if you only have access and secrete keys (and possibly endpoint url, config, etc.) -# then use the class method from_s3_resource_kwargs to construct. -# -# :param bucket_name: Bucket name (string) -# :param _s3_bucket: boto3 s3.Bucket object. -# :param _prefix: prefix that all accessed keys should have -# """ -# self.bucket_name = bucket_name -# self._s3_bucket = _s3_bucket -# self._prefix = _prefix -# -# @classmethod -# def from_s3_resource_kwargs(cls, -# bucket_name, -# aws_access_key_id, -# aws_secret_access_key, -# _prefix: str = '', -# endpoint_url=DFLT_AWS_S3_ENDPOINT, -# verify=DFLT_BOTO_CLIENT_VERIFY, -# config=DFLT_CONFIG): -# s3_resource = get_s3_resource(aws_access_key_id=aws_access_key_id, -# aws_secret_access_key=aws_secret_access_key, -# endpoint_url=endpoint_url, -# verify=verify, -# config=config) -# return cls.from_s3_resource(bucket_name, s3_resource, _prefix=_prefix) -# -# @classmethod -# def from_s3_resource(cls, -# bucket_name, -# s3_resource, -# _prefix=''): -# s3_bucket = s3_resource.Bucket(bucket_name) -# return cls(bucket_name, s3_bucket, _prefix=_prefix) - -# StoreInterface, S3BucketCollection, S3BucketRWD, StoreMutableMapping -# from py2store.base import StoreMutableMapping - - -# class Store(StoreBaseMixin, S3BucketRWD, PrefixRelativizationMixin, S3BucketCollection, IdentityKvWrapMixin): -# pass - -# from py2store.base import StoreBase, Store - -# class S3BucketStoreBase(S3BucketCollection, StoreBaseMixin, StringKvWrap, StoreBase): -# pass -# -# -# class S3BucketStoreNoOverwrites(OverWritesNotAllowedMixin, S3BucketStore): -# pass - - -# -# class RelativePathFormatStore(PrefixRelativizationMixin, PathFormatStore): -# pass -# -# -# from py2store.core import PrefixRelativizationMixin -# -# -# -# # StoreInterface, FilepathFormatKeys, LocalFileRWD, StoreMutableMapping -# # class S3BucketCollection(PrefixRelativizationMixin, S3BucketDacc, S3BucketCollection): -# # pass -# -# -# class S3BucketReader(PrefixRelativizationMixin, S3BucketDacc, S3BucketReaderMixin): -# """ Adds a __getitem__ to S3BucketDacc, which returns a bucket's object binary data for a key.""" -# pass -# -# -# class S3BucketSource(PrefixRelativizationMixin, AbstractObjSource, S3BucketCollection, S3BucketReaderMixin, S3BucketDacc): -# """ -# A S3BucketDacc mapping (i.e. a collection (iterable, sizable container) that has a reader (__getitem__), -# and mapping mixin methods such as get, keys, items, values, __eq__ and __ne__. -# """ -# pass -# -# -# class S3BucketWriter(PrefixRelativizationMixin, S3BucketDacc, S3BucketWriterMixin): -# """ A S3BucketDacc that can write to s3 and delete keys (and data) """ -# pass -# -# -# class S3BucketWriterNoOverwrites(OverWritesNotAllowedMixin, S3BucketWriter): -# """ -# Exactly like S3BucketWriter, but where writes to an already existing key are protected. -# If a key already exists, __setitem__ will raise a OverWritesNotAllowedError -# """ -# pass -# -# -# -# -# class S3BucketStore(PrefixRelativizationMixin, S3BucketDacc, AbstractObjStore, S3BucketCollection, -# S3BucketReaderMixin, S3BucketWriterMixin): -# """ -# A S3BucketDacc MutableMapping. -# That is, a S3BucketDacc that can read and write, as well as iterate -# """ -# pass -# -# -# class S3BucketStoreNoOverwrites(OverWritesNotAllowedMixin, S3BucketStore): -# """ -# A S3BucketDacc MutableMapping. -# That is, a S3BucketDacc that can read and write, as well as iterate -# """ -# pass -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/stores/sql_w_sqlalchemy.html b/docs/_modules/py2store/stores/sql_w_sqlalchemy.html deleted file mode 100644 index 8905065..0000000 --- a/docs/_modules/py2store/stores/sql_w_sqlalchemy.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - - py2store.stores.sql_w_sqlalchemy — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.stores.sql_w_sqlalchemy

-from functools import wraps
-
-from py2store.base import Store
-from py2store.persisters.sql_w_sqlalchemy import (
-    SQLAlchemyPersister,
-    SqlDbReader,
-)
-from py2store.util import lazyprop
-
-
-
[docs]class SQLAlchemyStore(Store): - @wraps(SQLAlchemyPersister.__init__) - def __init__(self, *args, **kwargs): - persister = SQLAlchemyPersister(*args, **kwargs) - super().__init__(persister)
- - -
[docs]class SQLAlchemyTupleStore(SQLAlchemyStore): - @lazyprop - def _key_fields(self): - return self.store._key_fields - - def _id_of_key(self, k): - return { - field: field_val for field, field_val in zip(self._key_fields, k) - } - - def _key_of_id(self, obj): - return tuple(getattr(obj, x) for x in self._key_fields) - - @lazyprop - def _data_fields(self): - return self.store._data_fields - - def _data_of_obj(self, data): - return { - field: field_val - for field, field_val in zip(self._data_fields, data) - } - - def _obj_of_data(self, obj): - return tuple(getattr(obj, x) for x in self._data_fields)
- - -# Extras ############################################################################################################### -# Note: The stuff below may require extra dependencies - -from py2store.util import ModuleNotFoundErrorNiceMessage - -with ModuleNotFoundErrorNiceMessage(): - import pandas as pd - -
[docs] class DfSqlDbReader(SqlDbReader): - def __getitem__(self, k): - table = super().__getitem__(k) - return pd.DataFrame(data=list(table), columns=table.column_names)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/test/util.html b/docs/_modules/py2store/test/util.html deleted file mode 100644 index 9dd01e6..0000000 --- a/docs/_modules/py2store/test/util.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - py2store.test.util — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.test.util

-"""
-utils for testing
-"""
-import os
-import random
-import string
-from functools import reduce
-from operator import add
-
-from py2store.key_mappers.tuples import (
-    dict_of_tuple,
-    str_of_tuple,
-    dsv_of_list,
-)
-from py2store.key_mappers.str_utils import (
-    n_format_params_in_str_format,
-    empty_arg_and_kwargs_for_format,
-)
-
-# Note: Probably want to use another package for generation of fake data.
-#   For example, https://github.com/joke2k/faker
-
-lower_case_letters = string.ascii_lowercase
-alphanumeric = string.digits + lower_case_letters
-non_alphanumeric = ''.join(set(string.printable).difference(alphanumeric))
-
-
-
[docs]def random_word(length, alphabet, concat_func=add): - """Make a random word by concatenating randomly drawn elements from alphabet together - Args: - length: Length of the word - alphabet: Alphabet to draw from - concat_func: The concatenation function (e.g. + for strings and lists) - - Note: Repeated elements in alphabet will have more chances of being drawn. - - Returns: - A word (whose type depends on what concatenating elements from alphabet produces). - - Not making this a proper doctest because I don't know how to seed the global random temporarily - >>> t = random_word(4, 'abcde'); # e.g. 'acae' - >>> t = random_word(5, ['a', 'b', 'c']); # e.g. 'cabba' - >>> t = random_word(4, [[1, 2, 3], [40, 50], [600], [7000]]); # e.g. [40, 50, 7000, 7000, 1, 2, 3] - >>> t = random_word(4, [1, 2, 3, 4]); # e.g. 13 (because adding numbers...) - >>> # ... sometimes it's what you want: - >>> t = random_word(4, [2 ** x for x in range(8)]); # e.g. 105 (binary combination) - >>> t = random_word(4, [1, 2, 3, 4], concat_func=lambda x, y: str(x) + str(y)); # e.g. '4213' - >>> t = random_word(4, [1, 2, 3, 4], concat_func=lambda x, y: int(str(x) + str(y))); # e.g. 3432 - """ - if isinstance(alphabet, bytes) or isinstance(alphabet[0], bytes): - # convert to list of bytes, or the function will return ints instead of bytes - alphabet = _list_of_bytes_singletons(alphabet) - return reduce( - concat_func, (random.choice(alphabet) for _ in range(length)) - )
- - -def _list_of_bytes_singletons(bytes_alphabet): - """Convert to list of bytes, or the function will return ints instead of bytes""" - return list(map(lambda x: bytes([x]), bytes_alphabet)) - - -
[docs]def random_string(length=7, alphabet=lower_case_letters): - """Same as random_word, but it optimized for strings - (5-10% faster for words of length 7, 25-30% faster for words of size 1000)""" - return ''.join(random.choice(alphabet) for _ in range(length))
- - -
[docs]def random_word_gen( - word_size_range=(1, 10), alphabet=lower_case_letters, n=100 -): - """Random string generator - Args: - word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes - alphabet: A string or iterable defining the alphabet to draw from - n: The number of elements the generator will yield - - Returns: - Random string generator - """ - if isinstance(word_size_range, int): - word_size_range = range(1, word_size_range + 1) - elif not isinstance(word_size_range, range): - word_size_range = range(*word_size_range) - - for _ in range(n): - length = random.choice(word_size_range) - yield random_word(length, alphabet)
- - -
[docs]def random_tuple_gen( - tuple_length=3, - word_size_range=(1, 10), - alphabet=lower_case_letters, - n: int = 100, -): - """Random tuple (of strings) generator - - Args: - tuple_length: The length of the tuples generated - word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes - alphabet: A string or iterable defining the alphabet to draw from - n: The number of elements the generator will yield - - Returns: - Random tuple (of strings) generator - """ - for _ in range(n): - yield tuple(random_word_gen(word_size_range, alphabet, tuple_length))
- - -
[docs]def random_dict_gen( - fields=('a', 'b', 'c'), - word_size_range=(1, 10), - alphabet=lower_case_letters, - n: int = 100, -): - """Random dict (of strings) generator - - Args: - fields: Field names for the random dicts - word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes - alphabet: A string or iterable defining the alphabet to draw from - n: The number of elements the generator will yield - - Returns: - Random dict (of strings) generator - """ - tuple_length = len(fields) - yield from ( - dict_of_tuple(x, fields) - for x in random_tuple_gen(tuple_length, word_size_range, alphabet, n) - )
- - -
[docs]def random_formatted_str_gen( - format_string='root/{}/{}_{}.test', - word_size_range=(1, 10), - alphabet=lower_case_letters, - n=100, -): - """Random formatted string generator - - Args: - format_string: A format string - word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes - alphabet: A string or iterable defining the alphabet to draw from - n: The number of elements the generator will yield - - Returns: - Yields random strings of the format defined by format_string - - Examples: - # >>> list(random_formatted_str_gen('root/{}/{}_{}.test', (2, 5), 'abc', n=5)) - [('root/acba/bb_abc.test',), - ('root/abcb/cbbc_ca.test',), - ('root/ac/ac_cc.test',), - ('root/aacc/ccbb_ab.test',), - ('root/aab/abb_cbab.test',)] - - >>> # The following will be made not random (by restricting the constraints to "no choice" - >>> # ... this is so that we get consistent outputs to assert for the doc test. - >>> - >>> # Example with automatic specification - >>> list(random_formatted_str_gen('root/{}/{}_{}.test', (3, 4), 'a', n=2)) - [('root/aaa/aaa_aaa.test',), ('root/aaa/aaa_aaa.test',)] - >>> - >>> # Example with manual specification - >>> list(random_formatted_str_gen('indexed field: {0}: named field: {name}', (2, 3), 'z', n=1)) - [('indexed field: zz: named field: zz',)] - """ - args_template, kwargs_template = empty_arg_and_kwargs_for_format( - format_string - ) - n_args = len(args_template) - args_gen = random_tuple_gen(n_args, word_size_range, alphabet, n) - kwargs_gen = random_dict_gen( - kwargs_template.keys(), word_size_range, alphabet, n - ) - yield from zip( - format_string.format(*args, **kwargs) - for args, kwargs in zip(args_gen, kwargs_gen) - )
- - -######################################################################################################################## -# s3 utils - - -def extract_s3_access_info(access_dict): - return { - 'bucket_name': access_dict['bucket'], - 'aws_access_key_id': access_dict['access'], - 'aws_secret_access_key': access_dict['secret'], - } - - -def _s3_env_var_name(kind, perm='RO'): - kind = kind.upper() - perm = perm.upper() - assert kind in { - 'BUCKET', - 'ACCESS', - 'SECRET', - }, "kind should be in {'BUCKET', 'ACCESS', 'SECRET'}" - assert perm in {'RW', 'RO'}, "perm should be in {'RW', 'RO'}" - return 'S3_TEST_{kind}_{perm}'.format(kind=kind, perm=perm) - - -def get_s3_test_access_info_from_env_vars(perm=None): - if perm is None: - try: - return get_s3_test_access_info_from_env_vars(perm='RO') - except LookupError: - return get_s3_test_access_info_from_env_vars(perm='RW') - else: - access_keys = dict() - for kind in {'BUCKET', 'ACCESS', 'SECRET'}: - k = _s3_env_var_name(kind, perm) - if k not in os.environ: - raise LookupError( - "Couldn't find the environment variable: {}".format(k) - ) - else: - access_keys[kind.lower()] = os.environ[k] - return extract_s3_access_info(access_keys) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/trans.html b/docs/_modules/py2store/trans.html deleted file mode 100644 index f31c029..0000000 --- a/docs/_modules/py2store/trans.html +++ /dev/null @@ -1,2577 +0,0 @@ - - - - - - - - py2store.trans — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.trans

-from functools import wraps, partial, reduce
-import types
-from inspect import signature, Parameter
-from typing import Union, Iterable, Optional, Collection
-from py2store.base import Store, KvReader, AttrNames
-from py2store.util import lazyprop, num_of_args, attrs_of, wraps
-from py2store.utils.signatures import Sig, KO
-from warnings import warn
-from collections.abc import Iterable
-
-
-########################################################################################################################
-# Internal Utils
-
-def _all_but_first_arg_are_keyword_only(func):
-    """
-    >>> def foo(a, *, b, c=2): ...
-    >>> _all_but_first_arg_are_keyword_only(foo)
-    True
-    >>> def bar(a, b, *, c=2): ...
-    >>> _all_but_first_arg_are_keyword_only(bar)
-    False
-    """
-    kinds = (p.kind for p in signature(func).parameters.values())
-    _ = next(kinds)  # consume first item, and all remaining should be KEYWORD_ONLY
-    return all(kind == Parameter.KEYWORD_ONLY for kind in kinds)
-
-
-# FIXME: doctest line numbers not shown correctly when wrapped by store_decorator!
-
[docs]def store_decorator(func): - """Helper to make store decorators. - - You provide a class-decorating function ``func`` that takes a store type (and possibly additional params) - and returns another decorated store type. - - ``store_decorator`` takes that ``func`` and provides an enhanced class decorator specialized for stores. - Namely it will: - - Add ``__module__``, ``__qualname__``, ``__name__`` and ``__doc__`` arguments to it - - Copy the aforementioned arguments to the decorated class, or copy the attributes of the original if not specified. - - Output a decorator that can be used in four different ways: a class/instance decorator/factory. - - By class/instance decorator/factory we mean that if ``A`` is a class, ``a`` an instance of it, - and ``deco`` a decorator obtained with ``store_decorator(func)``, - we can use ``deco`` to - - class decorator: decorate a class - - class decorator factory: make a function that decorates classes - - instance decorator: decorate an instance of a store - - instancce decorator factor: make a function that decorates instances of stores - - For example, say we have the following ``deco`` that we made with ``store_decorator``: - - >>> @store_decorator - ... def deco(cls=None, *, x=1): - ... # do stuff to cls, or a copy of it... - ... cls.x = x # like this for example - ... return cls - - And a class that has nothing to it: - - >>> class A: ... - - Nammely, it doesn't have an ``x`` - - >>> hasattr(A, 'x') - False - - We make a ``decorated_A`` with ``deco`` (class decorator example) - - >>> deco(A, x=42) - <class 'trans.A'> - - and we see that we now have an ``x`` and it's 42 - - >>> hasattr(A, 'x') - True - >>> A.x - 42 - - But we could have also made a factory to decorate ``A`` and anything else that comes our way. - - >>> paint_it_42 = deco(x=42) - >>> decorated_A = paint_it_42(A) - >>> assert decorated_A.x == 42 - >>> class B: - ... x = 'destined to disappear' - >>> assert paint_it_42(B).x == 42 - - To be fair though, you'll probably see the factory usage appear in the following form, - where the class is decorated at definition time. - - >>> @deco(x=42) - ... class B: - ... pass - >>> assert B.x == 42 - - If your exists already, and you want to keep it as is (with the same name), you can - use subclassing to transform a copy of ``A`` instead, as below. - Also note in the following example, that ``deco`` was used without parentheses, - which is equivalent to ``@deco()``, - and yes, store_decorator makes that possible to, as long as your params have defaults - - >>> @deco - ... class decorated_A(A): - ... pass - >>> assert decorated_A.x == 1 - >>> assert A.x == 42 - - Finally, you can also decorate instances: - - >>> class A: ... - >>> a = A() - >>> hasattr(a, 'x') - False - >>> b = deco(a); assert b.x == 1; # b has an x and it's 1 - >>> b = deco()(a); assert b.x == 1; # b has an x and it's 1 - >>> b = deco(a, x=42); assert b.x == 42 # b has an x and it's 42 - >>> b = deco(x=42)(a); assert b.x == 42; # b has an x and it's 42 - - WARNING: Note though that the type of ``b`` is not the same type as ``a`` - >>> isinstance(b, a.__class__) - False - - No, ``b`` is an instance of a ``py2store.base.Store``, which is a class containing an - instance of a store (here, ``a``). - - >>> type(b) - <class 'py2store.base.Store'> - >>> b.store == a - True - - Now, here's some more example, slightly closer to real usage - - >>> from py2store.trans import store_decorator - >>> from inspect import signature - >>> - >>> def rm_deletion(store=None, *, msg='Deletions not allowed.'): - ... name = getattr(store, '__name__', 'Something') + '_w_sommething' - ... assert isinstance(store, type), f"Should be a type, was {type(store)}: {store}" - ... wrapped_store = type(name, (store,), {}) - ... wrapped_store.__delitem__ = lambda self, k: msg - ... return wrapped_store - ... - >>> remove_deletion = store_decorator(rm_deletion) - - See how the signature of the wrapper has some extra inputs that were injected (__module__, __qualname__, etc.): - - >>> print(str(signature(remove_deletion))) - (store=None, *, msg='Deletions not allowed.', __module__=None, __name__=None, __qualname__=None, __doc__=None, __annotations__=None, __defaults__=None, __kwdefaults__=None) - - Using it as a class decorator factory (the most common way): - - As a class decorator "factory", without parameters (and without ()): - - >>> from collections import UserDict - >>> @remove_deletion - ... class WD(UserDict): - ... "Here's the doc" - ... pass - >>> wd = WD(x=5, y=7) - >>> assert wd == UserDict(x=5, y=7) # same as far as dict comparison goes - >>> assert wd.__delitem__('x') == 'Deletions not allowed.' - >>> assert wd.__doc__ == "Here's the doc" - - As a class decorator "factory", with parameters: - - >>> @remove_deletion(msg='No way. I do not trust you!!') - ... class WD(UserDict): ... - >>> wd = WD(x=5, y=7) - >>> assert wd == UserDict(x=5, y=7) # same as far as dict comparison goes - >>> assert wd.__delitem__('x') == 'No way. I do not trust you!!' - - The __doc__ is empty: - - >>> assert WD.__doc__ == None - - But we could specify a doc if we wanted to: - - >>> @remove_deletion(__doc__="Hi, I'm a doc.") - ... class WD(UserDict): - ... "This is the original doc, that will be overritten" - >>> assert WD.__doc__ == "Hi, I'm a doc." - - - The class decorations above are equivalent to the two following: - - >>> WD = remove_deletion(UserDict) - >>> wd = WD(x=5, y=7) - >>> assert wd == UserDict(x=5, y=7) # same as far as dict comparison goes - >>> assert wd.__delitem__('x') == 'Deletions not allowed.' - >>> - >>> WD = remove_deletion(UserDict, msg='No way. I do not trust you!!') - >>> wd = WD(x=5, y=7) - >>> assert wd == UserDict(x=5, y=7) # same as far as dict comparison goes - >>> assert wd.__delitem__('x') == 'No way. I do not trust you!!' - - But we can also decorate instances. In this case they will be wrapped in a Store class - before being passed on to the actual decorator. - - >>> d = UserDict(x=5, y=7) - >>> wd = remove_deletion(d) - >>> assert wd == d # same as far as dict comparison goes - >>> assert wd.__delitem__('x') == 'Deletions not allowed.' - >>> - >>> d = UserDict(x=5, y=7) - >>> wd = remove_deletion(d, msg='No way. I do not trust you!!') - >>> assert wd == d # same as far as dict comparison goes - >>> assert wd.__delitem__('x') == 'No way. I do not trust you!!' - - """ - - # wrapper_assignments = ('__module__', '__qualname__', '__name__', '__doc__', '__annotations__') - wrapper_assignments = ( - '__module__', '__name__', '__qualname__', '__doc__', - '__annotations__', '__defaults__', '__kwdefaults__') - - @wraps(func) - def _func_wrapping_store_in_cls_if_not_type(store, **kwargs): - - specials = dict() - for a in wrapper_assignments: - v = kwargs.pop(a, getattr(store, a, None)) - if v is not None: - specials[a] = v - - if not isinstance(store, type): - store_instance = store - WrapperStore = func(Store, **kwargs) - r = WrapperStore(store_instance) - else: - assert _all_but_first_arg_are_keyword_only(func), ( - "To use decorating_store_cls, all but the first of your function's arguments need to be all keyword only. " - f"The signature was {func.__qualname__}{signature(func)}") - r = func(store, **kwargs) - - for k, v in specials.items(): - if v is not None: - setattr(r, k, v) - - return r - - _func_wrapping_store_in_cls_if_not_type.func = func - - # @wraps(func) - wrapper_sig = Sig(func).merge_with_sig( - [dict(name=a, default=None, kind=KO) for a in wrapper_assignments], - ch_to_all_pk=False - ) - - @wrapper_sig - def wrapper(store=None, **kwargs): - if store is None: # then we want a factory - return partial(_func_wrapping_store_in_cls_if_not_type, **kwargs) - else: - wrapped_store_cls = _func_wrapping_store_in_cls_if_not_type(store, **kwargs) - - return wrapped_store_cls - - # Make sure the wrapper (yes, also the wrapper) has the same key dunders as the func - for a in wrapper_assignments: - v = getattr(func, a, None) - if v is not None: - setattr(wrapper, a, v) - - return wrapper
- - -def ensure_set(x): - if isinstance(x, str): - x = [x] - return set(x) - - -def get_class_name(cls, dflt_name=None): - name = getattr(cls, "__qualname__", None) - if name is None: - name = getattr(getattr(cls, "__class__", object), "__qualname__", None) - if name is None: - if dflt_name is not None: - return dflt_name - else: - raise ValueError(f"{cls} has no name I could extract") - return name - - -def store_wrap(obj): - if isinstance(obj, type): - class StoreWrap(Store): - @wraps(obj.__init__) - def __init__(self, *args, **kwargs): - persister = obj(*args, **kwargs) - super().__init__(persister) - - return StoreWrap - else: - return Store(obj) - - -# # Older version, kept around for awhile, for review: -# def store_wrap(obj, name=None): -# if isinstance(obj, type): -# name = name or f"{get_class_name(obj, 'StoreWrap')}Store" -# -# class StoreWrap(Store): -# @wraps(obj.__init__) -# def __init__(self, *args, **kwargs): -# persister = obj(*args, **kwargs) -# super().__init__(persister) -# -# StoreWrap.__qualname__ = name -# # if hasattr(obj, '_cls_trans'): -# # StoreWrap._cls_trans = obj._cls_trans -# return StoreWrap -# else: -# return Store(obj) - - -def _is_bound(method): - return hasattr(method, "__self__") - - -def _first_param_is_an_instance_param(params): - return len(params) > 0 and list(params)[0] in self_names - - -# TODO: Add validation of func: That all but perhaps 1 argument (not counting self) has a default -def _has_unbound_self(func): - """ - - Args: - func: - - Returns: - - >>> def f1(x): ... - >>> assert _has_unbound_self(f1) == 0 - >>> - >>> def f2(self, x): ... - >>> assert _has_unbound_self(f2) == 1 - >>> - >>> f3 = lambda self, x: True - >>> assert _has_unbound_self(f3) == 1 - >>> - >>> class A: - ... def bar(self, x): ... - ... def foo(dacc, x): ... - >>> a = A() - >>> - >>> _has_unbound_self(a.bar) - 0 - >>> _has_unbound_self(a.foo) - 0 - >>> _has_unbound_self(A.bar) - 1 - >>> _has_unbound_self(A.foo) - 0 - >>> - """ - params = signature(func).parameters - if len(params) == 0: - # no argument, so we can't be wrapping anything!!! - raise ValueError( - "The function has no parameters, so I can't guess which one you want to wrap" - ) - elif not _is_bound(func) and _first_param_is_an_instance_param(params): - return True - else: - return False - - -def transparent_key_method(self, k): - return k - - -
[docs]def mk_kv_reader_from_kv_collection( - kv_collection, name=None, getitem=transparent_key_method -): - """Make a KvReader class from a Collection class. - - Args: - kv_collection: The Collection class - name: The name to give the KvReader class (by default, it will be kv_collection.__qualname__ + 'Reader') - getitem: The method that will be assigned to __getitem__. Should have the (self, k) signature. - By default, getitem will be transparent_key_method, returning the key as is. - This default is useful when you want to delegate the actual getting to a _obj_of_data wrapper. - - Returns: A KvReader class that subclasses the input kv_collection - """ - - name = name or kv_collection.__qualname__ + "Reader" - reader_cls = type( - name, (kv_collection, KvReader), {"__getitem__": getitem} - ) - return reader_cls
- - -def raise_disabled_error(functionality): - def disabled_function(*args, **kwargs): - raise ValueError(f"{functionality} is disabled") - - return disabled_function - - -def disable_delitem(o): - if hasattr(o, "__delitem__"): - o.__delitem__ = raise_disabled_error("deletion") - return o - - -def disable_setitem(o): - if hasattr(o, "__setitem__"): - o.__setitem__ = raise_disabled_error("writing") - return o - - -def mk_read_only(o): - return disable_delitem(disable_setitem(o)) - - -def is_iterable(x): - return isinstance(x, Iterable) - - -
[docs]def add_ipython_key_completions(store): - """Add tab completion that shows you the keys of the store. - Note: ipython already adds local path listing automatically, - so you'll still get those along with your valid store keys. - """ - - def _ipython_key_completions_(self): - return self.keys() - - if isinstance(store, type): - store._ipython_key_completions_ = _ipython_key_completions_ - else: - setattr( - store, - "_ipython_key_completions_", - types.MethodType(_ipython_key_completions_, store), - ) - return store
- - -from py2store.util import copy_attrs -from py2store.errors import OverWritesNotAllowedError - - -def disallow_overwrites(store, *, error_msg=None, disable_deletes=True): - assert isinstance(store, type), "store needs to be a type" - if hasattr(store, "__setitem__"): - - def __setitem__(self, k, v): - if k in self: - raise OverWritesNotAllowedError( - "key {} already exists and cannot be overwritten. " - "If you really want to write to that key, delete it before writing".format( - k - ) - ) - return super().__setitem__(k, v) - - -
[docs]class OverWritesNotAllowedMixin: - """Mixin for only allowing a write to a key if they key doesn't already exist. - Note: Should be before the persister in the MRO. - - >>> class TestPersister(OverWritesNotAllowedMixin, dict): - ... pass - >>> p = TestPersister() - >>> p['foo'] = 'bar' - >>> #p['foo'] = 'bar2' # will raise error - >>> p['foo'] = 'this value should not be stored' # doctest: +NORMALIZE_WHITESPACE - Traceback (most recent call last): - ... - py2store.errors.OverWritesNotAllowedError: key foo already exists and cannot be overwritten. - If you really want to write to that key, delete it before writing - >>> p['foo'] # foo is still bar - 'bar' - >>> del p['foo'] - >>> p['foo'] = 'this value WILL be stored' - >>> p['foo'] - 'this value WILL be stored' - """ - - @staticmethod - def wrap(cls): - # TODO: Consider moving to trans and making instances wrappable too - class NoOverWritesClass(OverWritesNotAllowedMixin, cls): - ... - - copy_attrs( - NoOverWritesClass, cls, ("__name__", "__qualname__", "__module__") - ) - return NoOverWritesClass - - def __setitem__(self, k, v): - if self.__contains__(k): - raise OverWritesNotAllowedError( - "key {} already exists and cannot be overwritten. " - "If you really want to write to that key, delete it before writing".format( - k - ) - ) - return super().__setitem__(k, v)
- - -######################################################################################################################## -# Caching keys - -# TODO: If a read-one-by-one (vs the current read all implementation) is necessary one day, -# see https://github.com/zahlman/indexify/blob/master/src/indexify.py for ideas -# but probably buffered (read by chunks) version of the later is better. -
[docs]@store_decorator -def cached_keys( - store=None, - *, - keys_cache: Union[callable, Collection] = list, - iter_to_container=None, # deprecated: use keys_cache instead - cache_update_method="update", - name: str = None, # TODO: might be able to be deprecated since included in store_decorator - __module__=None, # TODO: might be able to be deprecated since included in store_decorator -) -> Union[callable, KvReader]: - """Make a class that wraps input class's __iter__ becomes cached. - - Quite often we have a lot of keys, that we get from a remote data source, and don't want to have to ask for - them again and again, having them be fetched, sent over the network, etc. - So we need caching. - - But this caching is not the typical read caching, since it's __iter__ we want to cache, and that's a generator. - So we'll implement a store class decorator specialized for this. - - The following decorator, when applied to a class (that has an __iter__), will perform the __iter__ code, consuming - all items of the generator and storing them in _keys_cache, and then will yield from there every subsequent call. - - It is assumed, if you're using the cached_keys transformation, that you're dealing with static data - (or data that can be considered static for the life of the store -- for example, when conducting analytics). - If you ever need to refresh the cache during the life of the store, you can to delete _keys_cache like this: - ``` - del your_store._keys_cache - ``` - Once you do that, the next time you try to ask something about the contents of the store, it will actually do - a live query again, as for the first time. - - Note: The default keys_cache is list though in many cases, you'd probably should use set, or an explicitly - computer set instead. The reason list is used as the default is because (1) we didn't want to assume that - order did not matter (maybe it does to you) and (2) we didn't want to assume that your keys were hashable. - That said, if you're keys are hashable, and order does not matter, use set. That'll give you two things: - (a) your `key in store` checks will be faster (O(1) instead of O(n)) and (b) you'll enforce unicity of keys. - - Know also that if you precompute the keys you want to cache with a container that has an update - method (by default `update`) your cache updates will be faster and if the container you use has - a `remove` method, you'll be able to delete as well. - - Args: - store: The store instance or class to wrap (must have an __iter__), or None if you want a decorator. - keys_cache: An explicit collection of keys - iter_to_container: The function that will be applied to existing __iter__() and assigned to cache. - The default is list. Another useful one is the sorted function. - cache_update_method: Name of the keys_cache update method to use, if it is an attribute of keys_cache. - Note that this cache_update_method will be used only - if keys_cache is an explicit iterable and has that attribute - if keys_cache is a callable and has that attribute. - The default None - name: The name of the new class - - Returns: - If store is: - None: Will return a decorator that can be applied to a store - a store class: Will return a wrapped class that caches it's keys - a store instance: Will return a wrapped instance that caches it's keys - - The instances of such key-cached classes have some extra attributes: - _explicit_keys: The actual cache. An iterable container - update_keys_cache: Is called if a user uses the instance to mutate the store (i.e. write or delete). - - You have two ways of caching keys: - - By providing the explicit list of keys you want cache (and use) - - By providing a callable that will iterate through your store and collect an explicit list of keys - - Let's take a simple dict as our original store. - >>> source = dict(c=3, b=2, a=1) - - Specify an iterable, and it will be used as the cached keys - >>> cached = cached_keys(source, keys_cache='bc') - >>> list(cached.items()) # notice that the order you get things is also ruled by the cache - [('b', 2), ('c', 3)] - - Specify a callable, and it will apply it to the existing keys to make your cache - >>> list(cached_keys(source, keys_cache=sorted)) - ['a', 'b', 'c'] - - You can use the callable keys_cache specification to filter as well! - Oh, and let's demo the fact that if you don't specify the store, it will make a store decorator for you: - >>> cache_my_keys = cached_keys(keys_cache=lambda keys: list(filter(lambda k: k >= 'b', keys))) - >>> d = cache_my_keys(source) # used as to transform an instance - >>> list(d) - ['c', 'b'] - - Let's use that same `cache_my_keys` to decorate a class instead: - >>> cached_dict = cache_my_keys(dict) - >>> d = cached_dict(c=3, b=2, a=1) - >>> list(d) - ['c', 'b'] - - Note that there's still an underlying store (dict) that has the data: - >>> repr(d) # repr isn't wrapped, so you can still see your underlying dict - "{'c': 3, 'b': 2, 'a': 1}" - - And yes, you can still add elements, - >>> d['z'] = 26 - >>> list(d.items()) - [('c', 3), ('b', 2), ('z', 26)] - - do bulk updates, - >>> d.update({'more': 'of this'}, more_of='that') - >>> list(d.items()) - [('c', 3), ('b', 2), ('z', 26), ('more', 'of this'), ('more_of', 'that')] - - and delete... - >>> del d['more'] - >>> list(d.items()) - [('c', 3), ('b', 2), ('z', 26), ('more_of', 'that')] - - But careful! Know what you're doing if you try to get creative. Have a look at this: - >>> d['a'] = 100 # add an 'a' item - >>> d.update(and_more='of that') # update to add yet another item - >>> list(d.items()) - [('c', 3), ('b', 2), ('z', 26), ('more_of', 'that')] - - Indeed: No 'a' or 'and_more'. - - Now... they were indeed added. Or to be more precise, the value of the already existing a was changed, - and a new ('and_more', 'of that') item was indeed added in the underlying store: - >>> repr(d) - "{'c': 3, 'b': 2, 'a': 100, 'z': 26, 'more_of': 'that', 'and_more': 'of that'}" - - But you're not seeing it. - - Why? - - Because you chose to use a callable keys_cache that doesn't have an 'update' method. - When your _keys_cache attribute (the iterable cache) is not updatable itself, the - way updates work is that we iterate through the underlying store (where the updates actually took place), - and apply the keys_cache (callable) to that iterable. - - So what happened here was that you have your new 'a' and 'and_more' items, but your cached version of the - store doesn't see it because it's filtered out. On the other hand, check out what happens if you have - an updateable cache. - - Using `set` instead of `list`, after the `filter`. - - >>> cache_my_keys = cached_keys(keys_cache=set) - >>> d = cache_my_keys(source) # used as to transform an instance - >>> sorted(d) # using sorted because a set's order is not always the same - ['a', 'b', 'c'] - >>> d['a'] = 100 - >>> d.update(and_more='of that') # update to add yet another item - >>> sorted(d.items()) - [('a', 100), ('and_more', 'of that'), ('b', 2), ('c', 3)] - - This example was to illustrate a more subtle aspect of cached_keys. You would probably deal with - the filter concern in a different way in this case. But the rope is there -- it's your choice on how - to use it. - - And here's some more examples if that wasn't enough! - - >>> # Lets cache the keys of a dict. - >>> cached_dict = cached_keys(dict) - >>> d = cached_dict(a=1, b=2, c=3) - >>> # And you get a store that behaves as expected (but more speed and RAM) - >>> list(d) - ['a', 'b', 'c'] - >>> list(d.items()) # whether you iterate with .keys(), .values(), or .items() - [('a', 1), ('b', 2), ('c', 3)] - - This is where the keys are stored: - >>> d._keys_cache - ['a', 'b', 'c'] - - >>> # Let's demo the iter_to_container argument. The default is "list", which will just consume the iter in order - >>> sorted_dict = cached_keys(dict, keys_cache=list) - >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1}) - >>> list(s) # keys will be in the order they were defined - ['b', 'a', 'c'] - >>> sorted_dict = cached_keys(dict, keys_cache=sorted) - >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1}) - >>> list(s) # keys will be sorted - ['a', 'b', 'c'] - >>> sorted_dict = cached_keys(dict, keys_cache=lambda x: sorted(x, key=len)) - >>> s = sorted_dict({'bbb': 3, 'aa': 2, 'c': 1}) - >>> list(s) # keys will be sorted according to their length - ['c', 'aa', 'bbb'] - - If you change the keys (adding new ones with __setitem__ or update, or removing with pop or popitem) - then the cache is recomputed (the first time you use an operation that iterates over keys) - >>> d.update(d=4) # let's add an element (try d['d'] = 4 as well) - >>> list(d) - ['a', 'b', 'c', 'd'] - >>> d['e'] = 5 - >>> list(d.items()) # whether you iterate with .keys(), .values(), or .items() - [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] - - >>> @cached_keys - ... class A: - ... def __iter__(self): - ... yield from [1, 2, 3] - >>> # Note, could have also used this form: AA = cached_keys(A) - >>> a = A() - >>> list(a) - [1, 2, 3] - >>> a._keys_cache = ['a', 'b', 'c'] # changing the cache, to prove that subsequent listing will read from there - >>> list(a) # proof: - ['a', 'b', 'c'] - >>> - - >>> # Let's demo the iter_to_container argument. The default is "list", which will just consume the iter in order - >>> sorted_dict = cached_keys(dict, keys_cache=list) - >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1}) - >>> list(s) # keys will be in the order they were defined - ['b', 'a', 'c'] - >>> sorted_dict = cached_keys(dict, keys_cache=sorted) - >>> s = sorted_dict({'b': 3, 'a': 2, 'c': 1}) - >>> list(s) # keys will be sorted - ['a', 'b', 'c'] - >>> sorted_dict = cached_keys(dict, keys_cache=lambda x: sorted(x, key=len)) - >>> s = sorted_dict({'bbb': 3, 'aa': 2, 'c': 1}) - >>> list(s) # keys will be sorted according to their length - ['c', 'aa', 'bbb'] - """ - if iter_to_container is not None: - assert callable(iter_to_container) - warn( - "The argument name 'iter_to_container' is being deprecated in favor of the more general 'keys_cache'" - ) - # assert keys_cache == iter_to_container - - # if store is None: - # return partial( - # cached_keys, - # keys_cache=keys_cache, - # cache_update_method=cache_update_method, - # name=name, - # __module__=__module__, - # ) - # elif not isinstance(store, type): # then consider it to be an instance - # store_instance = store - # WrapperStore = cached_keys( - # Store, - # keys_cache=keys_cache, - # cache_update_method=cache_update_method, - # name=name, - # __module__=__module__, - # ) - # return WrapperStore(store_instance) - # else: - # store_cls = store - assert isinstance(store, type), f"store_cls must be a type, was a {type(store)}: {store}" - - # name = name or 'IterCached' + get_class_name(store_cls) - name = name or get_class_name(store) - __module__ = __module__ or getattr(store, "__module__", None) - - class cached_cls(store): - _keys_cache = None - - cached_cls.__name__ = name - - # cached_cls = type(name, (store_cls,), {"_keys_cache": None}) - - # The following class is not the class that will be returned, but the class from which we'll take the methods - # that will be copied in the class that will be returned. - @_define_keys_values_and_items_according_to_iter - class CachedIterMethods: - _explicit_keys = False - _updatable_cache = False - _iter_to_container = None - if hasattr(keys_cache, cache_update_method): - _updatable_cache = True - if is_iterable( - keys_cache - ): # if keys_cache is iterable, it is the cache instance itself. - _keys_cache = keys_cache - _explicit_keys = True - elif callable(keys_cache): - # if keys_cache is not iterable, but callable, we'll use it to make the keys_cache from __iter__ - _iter_to_container = keys_cache - - @lazyprop - def _keys_cache(self): - # print(iter_to_container) - return keys_cache( - super(cached_cls, self).__iter__() - ) # TODO: Should it be iter(super(...)? - - # if not callable(_explicit_keys): - - # If keys_cache_update is None (the default), the method 'update' will be searched for as above, - # and if not found, will fall back to None. - # if isinstance(keys_cache_update, str): - # if (_explicit_keys and hasattr(_explicit_keys, '__class__') - # and hasattr(_explicit_keys.__class__, keys_cache_update)): - # keys_cache_update = getattr(_explicit_keys.__class__, keys_cache_update) - - # if (_explicit_keys and hasattr(_explicit_keys, '__class__') - # and hasattr(_explicit_keys.__class__, 'update')): - # keys_cache_update = _explicit_keys.__class__.update - # - - @property - def _iter_cache(self): # for back-compatibility - warn( - "The new name for `_iter_cache` is `_keys_cache`. Start using that!", - DeprecationWarning, - ) - return self._keys_cache - - def __iter__(self): - # if getattr(self, '_keys_cache', None) is None: - # self._keys_cache = iter_to_container(super(cached_cls, self).__iter__()) - yield from self._keys_cache - - def __len__(self): - return len(self._keys_cache) - - def items(self): - for k in self._keys_cache: - yield k, self[k] - - def __contains__(self, k): - return k in self._keys_cache - - # The write and update stuff ################################################################### - - if _updatable_cache: - - def update_keys_cache(self, keys): - """updates the keys by calling the - """ - update_func = getattr( - self._keys_cache, cache_update_method - ) - update_func(self._keys_cache, keys) - - update_keys_cache.__doc__ = ( - "Updates the _keys_cache by calling its {} method" - ) - else: - - def update_keys_cache(self, keys): - """Updates the _keys_cache by deleting the attribute - """ - try: - del self._keys_cache - # print('deleted _keys_cache') - except AttributeError: - pass - - def __setitem__(self, k, v): - super(cached_cls, self).__setitem__(k, v) - # self.store[k] = v - if ( - k not in self - ): # just to avoid deleting the cache if we already had the key - self.update_keys_cache((k,)) - # Note: different construction performances: (k,)->10ns, [k]->38ns, {k}->50ns - - def update(self, other=(), **kwds): - # print(other, kwds) - # super(cached_cls, self).update(other, **kwds) - super_setitem = super(cached_cls, self).__setitem__ - for k in other: - # print(k, other[k]) - super_setitem(k, other[k]) - # self.store[k] = other[k] - self.update_keys_cache(other) - - for k, v in kwds.items(): - # print(k, v) - super_setitem(k, v) - # self.store[k] = v - self.update_keys_cache(kwds) - - def __delitem__(self, k): - self._keys_cache.remove(k) - super(cached_cls, self).__delitem__(k) - - # And this is where we add all the needed methods (for example, no __setitem__ won't be added if the original - # class didn't have one in the first place. - special_attrs = { - "update_keys_cache", - "_keys_cache", - "_explicit_keys", - "_updatable_cache", - } - for attr in special_attrs | ( - AttrNames.KvPersister - & attrs_of(cached_cls) - & attrs_of(CachedIterMethods) - ): - setattr(cached_cls, attr, getattr(CachedIterMethods, attr)) - - if __module__ is not None: - cached_cls.__module__ = __module__ - - if hasattr(store, '__doc__'): - cached_cls.__doc__ = store.__doc__ - - return cached_cls
- - -cache_iter = cached_keys # TODO: Alias, partial it and make it more like the original, for back compatibility. - - -
[docs]@store_decorator -def catch_and_cache_error_keys( - store=None, - *, - errors_caught=Exception, - error_callback=None, - use_cached_keys_after_completed_iter=True, -): - """Store that will cache keys as they're accessed, separating those that raised errors and those that didn't. - Getting a key will still through an error, but the access attempts will be collected in an ._error_keys attribute. - Successfful attemps will be stored in _keys_cache. - Retrieval iteration (items() or values()) will on the other hand, skip the error (while still caching it). - If the iteration completes (and use_cached_keys_after_completed_iter), the use_cached_keys flag is turned on, - which will result in the store now getting it's keys from the _keys_cache. - - >>> @catch_and_cache_error_keys( - ... error_callback=lambda store, key, err: print(f"Error with {key} key: {err}")) - ... class Blacklist(dict): - ... _black_list = {'black', 'list'} - ... - ... def __getitem__(self, k): - ... if k not in self._black_list: - ... return super().__getitem__(k) - ... else: - ... raise KeyError(f"Nope, that's from the black list!") - >>> - >>> s = Blacklist(black=7, friday=20, frenzy=13) - >>> list(s) - ['black', 'friday', 'frenzy'] - >>> list(s.items()) - Error with black key: "Nope, that's from the black list!" - [('friday', 20), ('frenzy', 13)] - >>> sorted(s) # sorting to get consistent output - ['frenzy', 'friday'] - - - See that? First we had three keys, then we iterated and got only 2 items (fortunately, - we specified an ``error_callback`` so we ccould see that the iteration actually dropped a key). - That's strange. And even stranger is the fact that when we list our keys again, we get only two. - - You don't like it? Neither do I. But - - It's not a completely outrageous behavior -- if you're talking to live data, it - often happens that you get more, or less, from one second to another. - - This store isn't meant to be long living, but rather meant to solve the problem of skiping - items that are problematic (for example, malformatted files), with a trace of - what was skipped and what's valid (in case we need to iterate again and don't want to - bear the hit of requesting values for keys we already know are problematic. - - Here's a little peep of what is happening under the hood. - Meet ``_keys_cache`` and ``_error_keys`` sets (yes, unordered -- so know it) that are meant - to acccumulate valid and problematic keys respectively. - - >>> s = Blacklist(black=7, friday=20, frenzy=13) - >>> list(s) - ['black', 'friday', 'frenzy'] - >>> s._keys_cache, s._error_keys - (set(), set()) - >>> s['friday'] - 20 - >>> s._keys_cache, s._error_keys - ({'friday'}, set()) - >>> s['black'] - Traceback (most recent call last): - ... - KeyError: "Nope, that's from the black list!" - >>> s._keys_cache, s._error_keys - ({'friday'}, {'black'}) - - But see that we still have the full list: - - >>> list(s) - ['black', 'friday', 'frenzy'] - - Meet ``use_cached_keys``: He's the culprit. It's a flag that indicates whether we should be - using the cached keys or not. Obviously, it'll start off being ``False``: - - >>> s.use_cached_keys - False - - Now we could set it to ``True`` manually to change the mode. - But know that this switch happens automatically (UNLESS you specify otherwise by saying: - ``use_cached_keys_after_completed_iter=False``) when ever you got through a - VALUE-PRODUCING iteration (i.e. entirely consuming `items()` or `values()`). - - >>> sorted(s.values()) # sorting to get consistent output - Error with black key: "Nope, that's from the black list!" - [13, 20] - - """ - - assert isinstance(store, type), f"store_cls must be a type, was a {type(store)}: {store}" - - # assert isinstance(store, Mapping), f"store_cls must be a Mapping. Was not. mro is {store.mro()}: {store}" - - # class cached_cls(store): - # _keys_cache = None - # _error_keys = None - - # The following class is not the class that will be returned, but the class from which we'll take the methods - # that will be copied in the class that will be returned. - # @_define_keys_values_and_items_according_to_iter - class CachedKeyErrorsStore(store): - - @wraps(store.__init__) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._error_keys = set() - self._keys_cache = set() - self.use_cached_keys = False - self.use_cached_keys_after_completed_iter = use_cached_keys_after_completed_iter - self.errors_caught = errors_caught - self.error_callback = error_callback - - def __getitem__(self, k): - if self.use_cached_keys: - return super().__getitem__(k) - else: - try: - v = super().__getitem__(k) - self._keys_cache.add(k) - return v - except self.errors_caught: - self._error_keys.add(k) - raise - - def __iter__(self): - # if getattr(self, '_keys_cache', None) is None: - # self._keys_cache = iter_to_container(super(cached_cls, self).__iter__()) - if self.use_cached_keys: - yield from self._keys_cache - else: - yield from super().__iter__() - - def __len__(self): - if self.use_cached_keys: - return len(self._keys_cache) - else: - return super().__len__() - - def items(self): - if self.use_cached_keys: - for k in self._keys_cache: - yield k, self[k] - else: - for k in self: - try: - yield k, self[k] - except self.errors_caught as err: - if self.error_callback is not None: - self.error_callback(store, k, err) - if self.use_cached_keys_after_completed_iter: - self.use_cached_keys = True - - def values(self): - if self.use_cached_keys: - yield from (self[k] for k in self._keys_cache) - else: - yield from (v for k, v in self.items()) - - def __contains__(self, k): - if self.use_cached_keys: - return k in self._keys_cache - else: - return super().__contains__(k) - - return CachedKeyErrorsStore
- - -def iterate_values_and_accumulate_non_error_keys( - store, - cache_keys_here: list, - errors_caught=Exception, - error_callback=None -): - for k in store: - try: - v = store[k] - cache_keys_here.append(k) - yield v - except errors_caught as err: - if error_callback is not None: - error_callback(store, k, err) - - -######################################################################################################################## -# Filtering iteration - - -def take_everything(key): - return True - - -# TODO: Factor out the method injection pattern (e.g. __getitem__, __setitem__ and __delitem__ are nearly identical) -
[docs]@store_decorator -def filt_iter( - store=None, *, - filt: Union[callable, Iterable] = take_everything, - name=None, __module__=None # TODO: might be able to be deprecated since included in store_decorator -): - """Make a wrapper that will transform a store (class or instance thereof) into a sub-store (i.e. subset of keys). - - Args: - filt: A callable or iterable: - callable: Boolean filter function. A func taking a key and and returns True iff the key should be included. - iterable: The collection of keys you want to filter "in" - name: The name to give the wrapped class - - Returns: A wrapper (that then needs to be applied to a store instance or class. - - >>> filtered_dict = filt_iter(filt=lambda k: (len(k) % 2) == 1)(dict) # keep only odd length keys - >>> - >>> s = filtered_dict({'a': 1, 'bb': object, 'ccc': 'a string', 'dddd': [1, 2]}) - >>> - >>> list(s) - ['a', 'ccc'] - >>> 'a' in s # True because odd (length) key - True - >>> 'bb' in s # False because odd (length) key - False - >>> assert s.get('bb', None) == None - >>> len(s) - 2 - >>> list(s.keys()) - ['a', 'ccc'] - >>> list(s.values()) - [1, 'a string'] - >>> list(s.items()) - [('a', 1), ('ccc', 'a string')] - >>> s.get('a') - 1 - >>> assert s.get('bb') is None - >>> s['x'] = 10 - >>> list(s.items()) - [('a', 1), ('ccc', 'a string'), ('x', 10)] - >>> try: - ... s['xx'] = 'not an odd key' - ... raise ValueError("This should have failed") - ... except KeyError: - ... pass - """ - - # if store is None: - if not callable(filt): # if filt is not a callable... - # ... assume it's the collection of keys you want and make a filter function to filter those "in". - assert next( - iter(filt) - ), "filt should be a callable, or an iterable" - keys_that_should_be_filtered_in = set(filt) - - def filt(k): - return k in keys_that_should_be_filtered_in - - __module__ = __module__ or getattr(store, "__module__", None) - - name = name or "Filtered" + get_class_name(store) - wrapped_cls = type(name, (store,), {}) - - def __iter__(self): - yield from filter( - filt, super(wrapped_cls, self).__iter__() - ) - - wrapped_cls.__iter__ = __iter__ - - _define_keys_values_and_items_according_to_iter(wrapped_cls) - - def __len__(self): - c = 0 - for _ in self.__iter__(): - c += 1 - return c - - wrapped_cls.__len__ = __len__ - - def __contains__(self, k): - if filt(k): - return super(wrapped_cls, self).__contains__(k) - else: - return False - - wrapped_cls.__contains__ = __contains__ - - if hasattr(wrapped_cls, "__getitem__"): - - def __getitem__(self, k): - if filt(k): - return super(wrapped_cls, self).__getitem__(k) - else: - raise KeyError(f"Key not in store: {k}") - - wrapped_cls.__getitem__ = __getitem__ - - if hasattr(wrapped_cls, "get"): - - def get(self, k, default=None): - if filt(k): - return super(wrapped_cls, self).get(k, default) - else: - return default - - wrapped_cls.get = get - - if hasattr(wrapped_cls, "__setitem__"): - - def __setitem__(self, k, v): - if filt(k): - return super(wrapped_cls, self).__setitem__(k, v) - else: - raise KeyError(f"Key not in store: {k}") - - wrapped_cls.__setitem__ = __setitem__ - - if hasattr(wrapped_cls, "__delitem__"): - - def __delitem__(self, k): - if filt(k): - return super(wrapped_cls, self).__delitem__(k) - else: - raise KeyError(f"Key not in store: {k}") - - wrapped_cls.__delitem__ = __delitem__ - - # if __module__ is not None: - # wrapped_cls.__module__ = __module__ - # - # if hasattr(collection_cls, '__doc__'): - # wrapped_cls.__doc__ = store.__doc__ - - return wrapped_cls
- - -######################################################################################################################## -# Wrapping keys and values - -self_names = frozenset(["self"]) - - -def _define_keys_values_and_items_according_to_iter(cls): - if hasattr(cls, "keys"): - def keys(self): - yield from self.__iter__() # TODO: Should it be iter(self)? - - cls.keys = keys - - if hasattr(cls, "values"): - def values(self): - yield from (self[k] for k in self) - - cls.values = values - - if hasattr(cls, "items"): - def items(self): - yield from ((k, self[k]) for k in self) - - cls.items = items - - return cls - - -# TODO: would like to keep dict_keys methods (like __sub__, isdisjoint). How do I do so? -class _DefineKeysValuesAndItemsAccordingToIter: - def keys(self): - yield from self.__iter__() # TODO: Should it be iter(self)? - - def values(self): - yield from (self[k] for k in self) - - def items(self): - yield from ((k, self[k]) for k in self) - - -
[docs]def kv_wrap_persister_cls(persister_cls, name=None): - """Make a class that wraps a persister into a py2store.base.Store, - - Args: - persister_cls: The persister class to wrap - - Returns: A Store wrapping the persister (see py2store.base) - - >>> A = kv_wrap_persister_cls(dict) - >>> a = A() - >>> a['one'] = 1 - >>> a['two'] = 2 - >>> a['three'] = 3 - >>> list(a.items()) - [('one', 1), ('two', 2), ('three', 3)] - >>> A # looks like a dict, but is not: - <class 'abc.dictPWrapped'> - >>> assert hasattr(a, '_obj_of_data') # for example, it has this magic method - >>> # If you overwrite the _obj_of_data method, you'll transform outcomming values with it. - >>> # For example, say the data you stored were minutes, but you want to get then in secs... - >>> a._obj_of_data = lambda data: data * 60 - >>> list(a.items()) - [('one', 60), ('two', 120), ('three', 180)] - >>> - >>> # And if you want to have class that has this weird "store minutes, retrieve seconds", you can do this: - >>> class B(kv_wrap_persister_cls(dict)): - ... def _obj_of_data(self, data): - ... return data * 60 - >>> b = B() - >>> b.update({'one': 1, 'two': 2, 'three': 3}) # you can write several key-value pairs at once this way! - >>> list(b.items()) - [('one', 60), ('two', 120), ('three', 180)] - >>> # Warning! Advanced under-the-hood chat coming up.... Note this: - >>> print(b) - {'one': 1, 'two': 2, 'three': 3} - >>> # What?!? Well, remember, printing an object calls the objects __str__, which usually calls __repr__ - >>> # The wrapper doesn't wrap those methods, since they don't have consistent behaviors. - >>> # Here you're getting the __repr__ of the underlying dict store, without the key and value transforms. - >>> - >>> # Say you wanted to transform the incoming minute-unit data, converting to secs BEFORE they were stored... - >>> class C(kv_wrap_persister_cls(dict)): - ... def _data_of_obj(self, obj): - ... return obj * 60 - >>> c = C() - >>> c.update(one=1, two=2, three=3) # yet another way you can write multiple key-vals at once - >>> list(c.items()) - [('one', 60), ('two', 120), ('three', 180)] - >>> print(c) # but notice that unlike when we printed b, here the stored data is actually transformed! - {'one': 60, 'two': 120, 'three': 180} - >>> - >>> # Now, just to demonstrate key transformation, let's say that we need internal (stored) keys to be upper case, - >>> # but external (the keys you see when listed) ones to be lower case, for some reason... - >>> class D(kv_wrap_persister_cls(dict)): - ... _data_of_obj = staticmethod(lambda obj: obj * 60) # to demonstrated another way of doing this - ... _key_of_id = lambda self, _id: _id.lower() # note if you don't specify staticmethod, 1st arg must be self - ... def _id_of_key(self, k): # a function definition like you're used to - ... return k.upper() - >>> d = D() - >>> d['oNe'] = 1 - >>> d.update(TwO=2, tHrEE=3) - >>> list(d.items()) # you see clean lower cased keys at the interface of the store - [('one', 60), ('two', 120), ('three', 180)] - >>> # but internally, the keys are all upper case - >>> print(d) # equivalent to print(d.store), so keys and values not wrapped (values were transformed before stored) - {'ONE': 60, 'TWO': 120, 'THREE': 180} - >>> - >>> # On the other hand, careful, if you gave the data directly to D, you wouldn't get that. - >>> d = D({'one': 1, 'two': 2, 'three': 3}) - >>> print(d) - {'one': 1, 'two': 2, 'three': 3} - >>> # Thus is because when you construct a D with the dict, it initializes the dicts data with it directly - >>> # before the key/val transformers are in place to do their jobs. - """ - - name = name or (persister_cls.__qualname__ + "PWrapped") - - cls = type(name, (Store,), {}) - - if hasattr(persister_cls, '__doc__'): - cls.__doc__ = persister_cls.__doc__ - - @wraps(persister_cls.__init__) - def __init__(self, *args, **kwargs): - super(cls, self).__init__(persister_cls(*args, **kwargs)) - - cls.__init__ = __init__ - - return cls
- - -def _wrap_outcoming( - store_cls: type, wrapped_method: str, trans_func: Optional[callable] = None -): - """Output-transforming wrapping of the wrapped_method of store_cls. - The transformation is given by trans_func, which could be a one (trans_func(x) - or two (trans_func(self, x)) argument function. - - Args: - store_cls: The class that will be transformed - wrapped_method: The method (name) that will be transformed. - trans_func: The transformation function. - wrap_arg_idx: The index of the - - Returns: Nothing. It transforms the class in-place - - >>> from py2store.trans import store_wrap - >>> S = store_wrap(dict) - >>> _wrap_outcoming(S, '_key_of_id', lambda x: f'wrapped_{x}') - >>> s = S({'a': 1, 'b': 2}) - >>> list(s) - ['wrapped_a', 'wrapped_b'] - >>> _wrap_outcoming(S, '_key_of_id', lambda self, x: f'wrapped_{x}') - >>> s = S({'a': 1, 'b': 2}); assert list(s) == ['wrapped_a', 'wrapped_b'] - >>> class A: - ... def __init__(self, prefix='wrapped_'): - ... self.prefix = prefix - ... def _key_of_id(self, x): - ... return self.prefix + x - >>> _wrap_outcoming(S, '_key_of_id', A(prefix='wrapped_')._key_of_id) - >>> s = S({'a': 1, 'b': 2}); assert list(s) == ['wrapped_a', 'wrapped_b'] - >>> - >>> S = store_wrap(dict) - >>> _wrap_outcoming(S, '_obj_of_data', lambda x: x * 7) - >>> s = S({'a': 1, 'b': 2}) - >>> list(s.values()) - [7, 14] - """ - if trans_func is not None: - wrapped_func = getattr(store_cls, wrapped_method) - - if not _has_unbound_self(trans_func): - # print(f"00000: {store_cls}: {wrapped_method}, {trans_func}, {wrapped_func}, {wrap_arg_idx}") - @wraps(wrapped_func) - def new_method(self, x): - # # Long form (for explanation) - # super_method = getattr(super(store_cls, self), wrapped_method) - # output_of_super_method = super_method(x) - # transformed_output_of_super_method = trans_func(output_of_super_method) - # return transformed_output_of_super_method - return trans_func( - getattr(super(store_cls, self), wrapped_method)(x) - ) - - else: - # print(f"11111: {store_cls}: {wrapped_method}, {trans_func}, {wrapped_func}, {wrap_arg_idx}") - @wraps(wrapped_func) - def new_method(self, x): - # # Long form (for explanation) - # super_method = getattr(super(store_cls, self), wrapped_method) - # output_of_super_method = super_method(x) - # transformed_output_of_super_method = trans_func(self, output_of_super_method) - # return transformed_output_of_super_method - return trans_func( - self, getattr(super(store_cls, self), wrapped_method)(x) - ) - - setattr(store_cls, wrapped_method, new_method) - - -def _wrap_ingoing( - store_cls, wrapped_method: str, trans_func: Optional[callable] = None -): - if trans_func is not None: - wrapped_func = getattr(store_cls, wrapped_method) - - if not _has_unbound_self(trans_func): - - @wraps(wrapped_func) - def new_method(self, x): - return getattr(super(store_cls, self), wrapped_method)( - trans_func(x) - ) - - else: - - @wraps(wrapped_func) - def new_method(self, x): - return getattr(super(store_cls, self), wrapped_method)( - trans_func(self, x) - ) - - setattr(store_cls, wrapped_method, new_method) - - -
[docs]def wrap_kvs( - store=None, - name=None, - *, - key_of_id=None, - id_of_key=None, - obj_of_data=None, - data_of_obj=None, - preset=None, - postget=None, - __module__=None, - outcoming_key_methods=(), - outcoming_value_methods=(), - ingoing_key_methods=(), - ingoing_value_methods=(), -): - r"""Make a Store that is wrapped with the given key/val transformers. - - Naming convention: - Morphemes: - key: outer key - _id: inner key - obj: outer value - data: inner value - Grammar: - Y_of_X: means that you get a Y output when giving an X input. Also known as X_to_Y. - - - Args: - store: Store class or instance - name: Name to give the wrapper class - key_of_id: The outcoming key transformation function. - Forms are `k = key_of_id(_id)` or `k = key_of_id(self, _id)` - id_of_key: The ingoing key transformation function. - Forms are `_id = id_of_key(k)` or `_id = id_of_key(self, k)` - obj_of_data: The outcoming val transformation function. - Forms are `obj = obj_of_data(data)` or `obj = obj_of_data(self, data)` - data_of_obj: The ingoing val transformation function. - Forms are `data = data_of_obj(obj)` or `data = data_of_obj(self, obj)` - preset: A function that is called before doing a `__setitem__`. - The function is called with both `k` and `v` as inputs, and should output a transformed value. - The intent use is to do ingoing value transformations conditioned on the key. - For example, you may want to serialize an object depending on if you're writing to a - '.csv', or '.json', or '.pickle' file. - Forms are `preset(k, obj)` or `preset(self, k, obj)` - postget: A function that is called after the value `v` for a key `k` is be `__getitem__`. - The function is called with both `k` and `v` as inputs, and should output a transformed value. - The intent use is to do outcoming value transformations conditioned on the key. - We already have `obj_of_data` for outcoming value trans, but cannot condition it's behavior on k. - For example, you may want to deserialize the bytes of a '.csv', or '.json', or '.pickle' in different ways. - Forms are `obj = postget(k, data)` or `obj = postget(self, k, data)` - - Returns: - - >>> def key_of_id(_id): - ... return _id.upper() - >>> def id_of_key(k): - ... return k.lower() - >>> def obj_of_data(data): - ... return data - 100 - >>> def data_of_obj(obj): - ... return obj + 100 - >>> - >>> A = wrap_kvs(dict, 'A', - ... key_of_id=key_of_id, id_of_key=id_of_key, obj_of_data=obj_of_data, data_of_obj=data_of_obj) - >>> a = A() - >>> a['KEY'] = 1 - >>> a # repr is just the base class (dict) repr, so shows "inside" the store (lower case keys and +100) - {'key': 101} - >>> a['key'] = 2 - >>> print(a) # repr is just the base class (dict) repr, so shows "inside" the store (lower case keys and +100) - {'key': 102} - >>> a['kEy'] = 3 - >>> a # repr is just the base class (dict) repr, so shows "inside" the store (lower case keys and +100) - {'key': 103} - >>> list(a) # but from the point of view of the interface the keys are all upper case - ['KEY'] - >>> list(a.items()) # and the values are those we put there. - [('KEY', 3)] - >>> - >>> # And now this: Showing how to condition the value transform (like obj_of_data), but conditioned on key. - >>> B = wrap_kvs(dict, 'B', postget=lambda k, v: f'upper {v}' if k[0].isupper() else f'lower {v}') - >>> b = B() - >>> b['BIG'] = 'letters' - >>> b['small'] = 'text' - >>> list(b.items()) - [('BIG', 'upper letters'), ('small', 'lower text')] - >>> - >>> - >>> # Let's try preset and postget. We'll wrap a dict and write the same list of lists object to - >>> # keys ending with .csv, .json, and .pkl, specifying the obvious extension-dependent - >>> # serialization/deserialization we want to associate with it. - >>> - >>> # First, some very simple csv transformation functions - >>> to_csv = lambda LoL: '\\n'.join(map(','.join, map(lambda L: (x for x in L), LoL))) - >>> from_csv = lambda csv: list(map(lambda x: x.split(','), csv.split('\\n'))) - >>> LoL = [['a','b','c'],['d','e','f']] - >>> assert from_csv(to_csv(LoL)) == LoL - >>> - >>> import json, pickle - >>> - >>> def preset(k, v): - ... if k.endswith('.csv'): - ... return to_csv(v) - ... elif k.endswith('.json'): - ... return json.dumps(v) - ... elif k.endswith('.pkl'): - ... return pickle.dumps(v) - ... else: - ... return v # as is - ... - ... - >>> def postget(k, v): - ... if k.endswith('.csv'): - ... return from_csv(v) - ... elif k.endswith('.json'): - ... return json.loads(v) - ... elif k.endswith('.pkl'): - ... return pickle.loads(v) - ... else: - ... return v # as is - ... - >>> mydict = wrap_kvs(dict, preset=preset, postget=postget) - >>> - >>> obj = [['a','b','c'],['d','e','f']] - >>> d = mydict() - >>> d['foo.csv'] = obj # store the object as csv - >>> d # "printing" a dict by-passes the transformations, so we see the data in the "raw" format it is stored in. - {'foo.csv': 'a,b,c\\nd,e,f'} - >>> d['foo.csv'] # but if we actually ask for the data, it deserializes to our original object - [['a', 'b', 'c'], ['d', 'e', 'f']] - >>> d['bar.json'] = obj # store the object as json - >>> d - {'foo.csv': 'a,b,c\\nd,e,f', 'bar.json': '[["a", "b", "c"], ["d", "e", "f"]]'} - >>> d['bar.json'] - [['a', 'b', 'c'], ['d', 'e', 'f']] - >>> d['bar.json'] = {'a': 1, 'b': [1, 2], 'c': 'normal json'} # let's write a normal json instead. - >>> d - {'foo.csv': 'a,b,c\\nd,e,f', 'bar.json': '{"a": 1, "b": [1, 2], "c": "normal json"}'} - >>> del d['foo.csv'] - >>> del d['bar.json'] - >>> d['foo.pkl'] = obj # 'save' obj as pickle - >>> d['foo.pkl'] - [['a', 'b', 'c'], ['d', 'e', 'f']] - - # TODO: Add tests for outcoming_key_methods etc. - """ - all_but_first_kwargs = dict( - name=name, - key_of_id=key_of_id, - id_of_key=id_of_key, - obj_of_data=obj_of_data, - data_of_obj=data_of_obj, - preset=preset, - postget=postget, - __module__=__module__, - outcoming_key_methods=outcoming_key_methods, - outcoming_value_methods=outcoming_value_methods, - ingoing_key_methods=ingoing_key_methods, - ingoing_value_methods=ingoing_value_methods, ) - - if store is None: - return partial(wrap_kvs, **all_but_first_kwargs) - elif not isinstance(store, type): # then consider it to be an instance - store_instance = store - WrapperStore = wrap_kvs(Store, **all_but_first_kwargs) - return WrapperStore(store_instance) - else: # it's a class we're wrapping - name = name or store.__qualname__ + "Wrapped" - - # TODO: This is not the best way to handle this. Investigate another way. ###################### - global_names = set(globals()).union(locals()) - if name in global_names: - raise NameError("That name is already in use") - # TODO: ######################################################################################## - - store_cls = kv_wrap_persister_cls(store, name=name) # experiment - store_cls._cls_trans = None - - def cls_trans(store_cls: type): - for method_name in {"_key_of_id"} | ensure_set(outcoming_key_methods): - _wrap_outcoming(store_cls, method_name, key_of_id) - - for method_name in {"_obj_of_data"} | ensure_set( - outcoming_value_methods - ): - _wrap_outcoming(store_cls, method_name, obj_of_data) - - for method_name in {"_id_of_key"} | ensure_set(ingoing_key_methods): - _wrap_ingoing(store_cls, method_name, id_of_key) - - for method_name in {"_data_of_obj"} | ensure_set( - ingoing_value_methods - ): - _wrap_ingoing(store_cls, method_name, data_of_obj) - - if postget is not None: - if num_of_args(postget) < 2: - raise ValueError( - "A postget function needs to have (key, value) or (self, key, value) arguments" - ) - - if not _has_unbound_self(postget): - - def __getitem__(self, k): - return postget(k, super(store_cls, self).__getitem__(k)) - - else: - - def __getitem__(self, k): - return postget( - self, k, super(store_cls, self).__getitem__(k) - ) - - store_cls.__getitem__ = __getitem__ - - if preset is not None: - if num_of_args(preset) < 2: - raise ValueError( - "A preset function needs to have (key, value) or (self, key, value) arguments" - ) - - if not _has_unbound_self(preset): - - def __setitem__(self, k, v): - return super(store_cls, self).__setitem__(k, preset(k, v)) - - else: - - def __setitem__(self, k, v): - return super(store_cls, self).__setitem__( - k, preset(self, k, v) - ) - - store_cls.__setitem__ = __setitem__ - - if __module__ is not None: - store_cls.__module__ = __module__ - - # add an attribute containing the cls_trans. - # This is is both for debugging and introspection use, - # as well as if we need to pass on the transformation in a recursive situation - store_cls._cls_trans = cls_trans - - return store_cls - - return cls_trans(store_cls)
- - -def _kv_wrap_outcoming_keys(trans_func): - """Transform 'out-coming' keys, that is, the keys you see when you ask for them, - say, through __iter__(), keys(), or first element of the items() pairs. - - Use this when you wouldn't use the keys in their original format, - or when you want to extract information from it. - - Warning: If you haven't also wrapped incoming keys with a corresponding inverse transformation, - you won't be able to use the outcoming keys to fetch data. - - >>> from collections import UserDict - >>> S = kv_wrap.outcoming_keys(lambda x: x[5:])(UserDict) - >>> s = S({'root/foo': 10, 'root/bar': 'xo'}) - >>> list(s) - ['foo', 'bar'] - >>> list(s.keys()) - ['foo', 'bar'] - - # TODO: Asymmetric key trans breaks getting items (therefore items()). Resolve (remove items() for asym keys?) - # >>> list(s.items()) - # [('foo', 10), ('bar', 'xo')] - """ - - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_kr" - ) - return wrap_kvs(o, name, key_of_id=trans_func) - - return wrapper - - -def _kv_wrap_ingoing_keys(trans_func): - """Transform 'in-going' keys, that is, the keys you see when you ask for them, - say, through __iter__(), keys(), or first element of the items() pairs. - - Use this when your context holds objects themselves holding key information, but you don't want to - (because you shouldn't) 'manually' extract that information and construct the key manually every time you need - to write something or fetch some existing data. - - Warning: If you haven't also wrapped outcoming keys with a corresponding inverse transformation, - you won't be able to use the incoming keys to fetch data. - - >>> from collections import UserDict - >>> S = kv_wrap.ingoing_keys(lambda x: 'root/' + x)(UserDict) - >>> s = S() - >>> s['foo'] = 10 - >>> s['bar'] = 'xo' - >>> list(s) - ['root/foo', 'root/bar'] - >>> list(s.keys()) - ['root/foo', 'root/bar'] - - # TODO: Asymmetric key trans breaks getting items (therefore items()). Resolve (remove items() for asym keys?) - # >>> list(s.items()) - # [('root/foo', 10), ('root/bar', 'xo')] - """ - - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_kw" - ) - return wrap_kvs(o, name, id_of_key=trans_func) - - return wrapper - - -def _kv_wrap_outcoming_vals(trans_func): - """Transform 'out-coming' values, that is, the values you see when you ask for them, - say, through the values() or the second element of items() pairs. - This can be seen as adding a de-serialization layer: trans_func being the de-serialization function. - - For example, say your store gives you values of the bytes type, but you want to use text, or gives you text, - but you want it to be interpreted as a JSON formatted text and get a dict instead. Both of these are - de-serialization layers, or out-coming value transformations. - - Warning: If it matters, make sure you also wrapped with a corresponding inverse serialization. - - >>> from collections import UserDict - >>> S = kv_wrap.outcoming_vals(lambda x: x * 2)(UserDict) - >>> s = S(foo=10, bar='xo') - >>> list(s.values()) - [20, 'xoxo'] - >>> list(s.items()) - [('foo', 20), ('bar', 'xoxo')] - """ - - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_vr" - ) - return wrap_kvs(o, name, obj_of_data=trans_func) - - return wrapper - - -def _kv_wrap_ingoing_vals(trans_func): - """Transform 'in-going' values, that is, the values at the level of the store's interface are transformed - to a different value before writing to the wrapped store. - This can be seen as adding a serialization layer: trans_func being the serialization function. - - For example, say you have a list of audio samples, and you want to save these in a WAV format. - - Warning: If it matters, make sure you also wrapped with a corresponding inverse de-serialization. - - >>> from collections import UserDict - >>> S = kv_wrap.ingoing_vals(lambda x: x * 2)(UserDict) - >>> s = S() - >>> s['foo'] = 10 - >>> s['bar'] = 'xo' - >>> list(s.values()) - [20, 'xoxo'] - >>> list(s.items()) - [('foo', 20), ('bar', 'xoxo')] - """ - - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_vw" - ) - return wrap_kvs(o, name, data_of_obj=trans_func) - - return wrapper - - -def _ingoing_vals_wrt_to_keys(trans_func): - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_vwk" - ) - return wrap_kvs(o, name, preset=trans_func) - - return wrapper - - -def _outcoming_vals_wrt_to_keys(trans_func): - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_vrk" - ) - return wrap_kvs(o, name, postget=trans_func) - - return wrapper - - -
[docs]def mk_trans_obj(**kwargs): - """Convenience method to quickly make a trans_obj (just an object holding some trans functions""" - # TODO: Could make this more flexible (assuming here only staticmethods) and validate inputs... - return type( - "TransObj", (), {k: staticmethod(v) for k, v in kwargs.items()} - )()
- - -
[docs]def kv_wrap(trans_obj): - """ - kv_wrap: A function that makes a wrapper (a decorator) that will get the wrappers from methods of the input object. - - kv_wrap also has attributes: - outcoming_keys, ingoing_keys, outcoming_vals, ingoing_vals, and val_reads_wrt_to_keys - which will only add a single specific wrapper (specified as a function), when that's what you need. - - """ - - key_of_id = getattr(trans_obj, "_key_of_id", None) - id_of_key = getattr(trans_obj, "_id_of_key", None) - obj_of_data = getattr(trans_obj, "_obj_of_data", None) - data_of_obj = getattr(trans_obj, "_data_of_obj", None) - preset = getattr(trans_obj, "_preset", None) - postget = getattr(trans_obj, "_postget", None) - - def wrapper(o, name=None): - name = ( - name - or getattr(o, "__qualname__", getattr(o.__class__, "__qualname__")) - + "_kr" - ) - return wrap_kvs( - o, - name, - key_of_id=key_of_id, - id_of_key=id_of_key, - obj_of_data=obj_of_data, - data_of_obj=data_of_obj, - preset=preset, - postget=postget, - ) - - return wrapper
- - -kv_wrap.mk_trans_obj = mk_trans_obj # to have a trans_obj maker handy -kv_wrap.outcoming_keys = _kv_wrap_outcoming_keys -kv_wrap.ingoing_keys = _kv_wrap_ingoing_keys -kv_wrap.outcoming_vals = _kv_wrap_outcoming_vals -kv_wrap.ingoing_vals = _kv_wrap_ingoing_vals -kv_wrap.ingoing_vals_wrt_to_keys = _ingoing_vals_wrt_to_keys -kv_wrap.outcoming_vals_wrt_to_keys = _outcoming_vals_wrt_to_keys - - -
[docs]def mk_wrapper(wrap_cls): - """ - - You have a wrapper class and you want to make a wrapper out of it, - that is, a decorator factory with which you can make wrappers, like this: - ``` - wrapper = mk_wrapper(wrap_cls) - ``` - that you can then use to transform stores like thiis: - ``` - MyStore = wrapper(**wrapper_kwargs)(StoreYouWantToTransform) - ``` - - :param wrap_cls: - :return: - - >>> class RelPath: - ... def __init__(self, root): - ... self.root = root - ... self._root_length = len(root) - ... def _key_of_id(self, _id): - ... return _id[self._root_length:] - ... def _id_of_key(self, k): - ... return self.root + k - >>> relpath_wrap = mk_wrapper(RelPath) - >>> RelDict = relpath_wrap(root='foo/')(dict) - >>> s = RelDict() - >>> s['bar'] = 42 - >>> assert list(s) == ['bar'] - >>> assert s['bar'] == 42 - >>> assert str(s) == "{'foo/bar': 42}" # reveals that actually, behind the scenes, there's a "foo/" prefix - """ - - @wraps(wrap_cls) - def wrapper(*args, **kwargs): - return kv_wrap(wrap_cls(*args, **kwargs)) - - return wrapper
- - -
[docs]def add_wrapper_method(wrap_cls=None, *, method_name="wrapper"): - """Decorator that adds a wrapper method (itself a decorator) to a wrapping class - Clear? - See `mk_wrapper` function and doctest example if not. - - What `add_wrapper_method` does is just to add a `"wrapper"` method - (or another name if you ask for it) to `wrap_cls`, so that you can use that - class for it's purpose of transforming stores more conveniently. - - :param wrap_cls: The wrapper class (the definitioin of the transformation. - If None, the functiion will make a decorator to decorate wrap_cls later - :param method_name: The method name you want to use (default is 'wrapper') - - >>> - >>> @add_wrapper_method - ... class RelPath: - ... def __init__(self, root): - ... self.root = root - ... self._root_length = len(root) - ... def _key_of_id(self, _id): - ... return _id[self._root_length:] - ... def _id_of_key(self, k): - ... return self.root + k - ... - >>> RelDict = RelPath.wrapper(root='foo/')(dict) - >>> s = RelDict() - >>> s['bar'] = 42 - >>> assert list(s) == ['bar'] - >>> assert s['bar'] == 42 - >>> assert str(s) == "{'foo/bar': 42}" # reveals that actually, behind the scenes, there's a "foo/" prefix - """ - if wrap_cls is None: - return partial(add_wrapper_method, method_name=method_name) - else: - setattr(wrap_cls, method_name, mk_wrapper(wrap_cls)) - return wrap_cls
- - -######################################################################################################################## -# Aliasing - -_method_name_for = { - "write": "__setitem__", - "read": "__getitem__", - "delete": "__delitem__", - "list": "__iter__", - "count": "__len__", -} - - -
[docs]def add_path_get(store=None, name=None, path_type: type = tuple): - """ - Make nested stores accessible through key paths. - - Say you have some nested stores. - You know... like a `ZipFileReader` store whose values are `ZipReader`s, - whose values are bytes of the zipped files (and you can go on... whose (json) values are...). - - Well, you can access any node of this nested tree of stores like this: - ``` - MyStore[key_1][key_2][key_3] - ``` - And that's fine. But maybe you'd like to do it this way instead: - ``` - MyStore[key_1, key_2, key_3] - ``` - Or like this: - - MyStore['key_1/key_2/key_3'] - Or this: - - MyStore['key_1.key_2.key_3'] - You get the point. This is what `add_path_get` is meant for. - - Args: - store: The store (class or instance) you're wrapping. - If not specified, the function will return a decorator. - name: The name to give the class (not applicable to instance wrapping) - path_type: The type that paths are expressed as. Needs to be an Iterable type. By default, a tuple. - This is used to decide whether the key should be taken as a "normal" key of the store, - or should be used to iterate through, recursively getting values. - - Returns: A wrapped store (class or instance), or a store wrapping decorator (if store is not specified) - - See Also: `py2store.key_mappers.paths.PathGetMixin`, `py2store.key_mappers.paths.KeyPath` - - >>> # wrapping an instance - >>> s = add_path_get({'a': {'b': {'c': 42}}}) - >>> s['a'] - {'b': {'c': 42}} - >>> s['a', 'b'] - {'c': 42} - >>> s['a', 'b', 'c'] - 42 - >>> # wrapping a class - >>> S = add_path_get(dict) - >>> s = S(a={'b': {'c': 42}}) - >>> assert s['a'] == {'b': {'c': 42}}; assert s['a', 'b'] == {'c': 42}; assert s['a', 'b', 'c'] == 42 - >>> - >>> # using add_path_get as a decorator - >>> @add_path_get - ... class S(dict): - ... pass - >>> s = S(a={'b': {'c': 42}}) - >>> assert s['a'] == {'b': {'c': 42}}; - >>> assert s['a', 'b'] == s['a']['b']; - >>> assert s['a', 'b', 'c'] == s['a']['b']['c'] - >>> - >>> # a different kind of path? - >>> # You can choose a different path_type, but sometimes (say both keys and key paths are strings) - >>> # you need to involve more tools. Like py2store.key_mappers.paths.KeyPath... - >>> from py2store.key_mappers.paths import KeyPath - >>> from py2store import kv_wrap - >>> SS = kv_wrap(KeyPath(path_sep='.'))(S) - >>> s = SS({'a': {'b': {'c': 42}}}) - >>> assert s['a'] == {'b': {'c': 42}}; assert s['a.b'] == s['a']['b']; assert s['a.b.c'] == s['a']['b']['c'] - """ - if store is None: - return partial(add_path_get, name=name, path_type=path_type) - elif not isinstance(store, type): # then consider it to be an instance - store_instance = store - WrapperStore = add_path_get(Store, name=name, path_type=path_type) - - return WrapperStore(store_instance) - else: # it's a class we're wrapping - name = name or store.__qualname__ + "WithPathGet" - - # TODO: This is not the best way to handle this. Investigate another way. ###################### - global_names = set(globals()).union(locals()) - if name in global_names: - raise NameError("That name is already in use") - # TODO: ######################################################################################## - - store_cls = kv_wrap_persister_cls(store, name=name) - store_cls._path_type = path_type - - def __getitem__(self, k): - if isinstance(k, self._path_type): - return reduce(lambda store, key: store[key], k, self) - else: - return super(store_cls, self).__getitem__(k) - - store_cls.__getitem__ = __getitem__ - - return store_cls
- - -def _insert_alias(store, method_name, alias=None): - if isinstance(alias, str) and hasattr(store, method_name): - setattr(store, alias, getattr(store, method_name)) - - -
[docs]def insert_aliases( - store, *, write=None, read=None, delete=None, list=None, count=None -): - """Insert method aliases of CRUD operations of a store (class or instance). - If store is a class, you'll get a copy of the class with those methods added. - If store is an instance, the methods will be added in place (no copy will be made). - - Note: If an operation (write, read, delete, list, count) is not specified, no alias will be created for - that operation. - - IMPORTANT NOTE: The signatures of the methods the aliases will point to will not change. - We say this because, you can call the write method "dump", but you'll have to use it as - `store.dump(key, val)`, not `store.dump(val, key)`, which is the signature you're probably used to - (it's the one used by json.dump or pickle.dump for example). If you want that familiar interface, - using the insert_load_dump_aliases function. - - Args: - store: The store to extend with aliases. - write: Desired method name for __setitem__ - read: Desired method name for __getitem__ - delete: Desired method name for __delitem__ - list: Desired method name for __iter__ - count: Desired method name for __len__ - - Returns: A store with the desired aliases. - - >>> # Example of extending a class - >>> mydict = insert_aliases(dict, write='dump', read='load', delete='rm', list='peek', count='size') - >>> s = mydict(true='love') - >>> s.dump('friends', 'forever') - >>> s - {'true': 'love', 'friends': 'forever'} - >>> s.load('true') - 'love' - >>> list(s.peek()) - ['true', 'friends'] - >>> s.size() - 2 - >>> s.rm('true') - >>> s - {'friends': 'forever'} - >>> - >>> # Example of extending an instance - >>> from collections import UserDict - >>> s = UserDict(true='love') # make (and instance) of a UserDict (can't modify a dict instance) - >>> # make aliases of note that you don't need - >>> s = insert_aliases(s, write='put', read='retrieve', count='num_of_items') - >>> s.put('friends', 'forever') - >>> s - {'true': 'love', 'friends': 'forever'} - >>> s.retrieve('true') - 'love' - >>> s.num_of_items() - 2 - """ - if isinstance(store, type): - store = type(store.__qualname__, (store,), {}) - for alias, method_name in _method_name_for.items(): - _insert_alias(store, method_name, alias=locals().get(alias)) - return store
- - -
[docs]def insert_load_dump_aliases(store, delete=None, list=None, count=None): - """Insert load and dump methods, with familiar dump(obj, location) signature. - - Args: - store: The store to extend with aliases. - delete: Desired method name for __delitem__ - list: Desired method name for __iter__ - count: Desired method name for __len__ - - Returns: A store with the desired aliases. - - >>> mydict = insert_load_dump_aliases(dict) - >>> s = mydict() - >>> s.dump(obj='love', key='true') - >>> s - {'true': 'love'} - """ - store = insert_aliases( - store, read="load", delete=delete, list=list, count=count - ) - - def dump(self, obj, key): - return self.__setitem__(key, obj) - - if isinstance(store, type): - store.dump = dump - else: - store.dump = types.MethodType(dump, store) - - return store
- - -########## To be deprecated ############################################################################################ - -# TODO: Factor out the method injection pattern (e.g. __getitem__, __setitem__ and __delitem__ are nearly identical) - -
[docs]def filtered_iter( - filt: Union[callable, Iterable], store=None, *, - name=None, __module__=None # TODO: might be able to be deprecated since included in store_decorator -): - """Make a wrapper that will transform a store (class or instance thereof) into a sub-store (i.e. subset of keys). - - Args: - filt: A callable or iterable: - callable: Boolean filter function. A func taking a key and and returns True iff the key should be included. - iterable: The collection of keys you want to filter "in" - name: The name to give the wrapped class - - Returns: A wrapper (that then needs to be applied to a store instance or class. - - >>> filtered_dict = filtered_iter(filt=lambda k: (len(k) % 2) == 1)(dict) # keep only odd length keys - >>> - >>> s = filtered_dict({'a': 1, 'bb': object, 'ccc': 'a string', 'dddd': [1, 2]}) - >>> - >>> list(s) - ['a', 'ccc'] - >>> 'a' in s # True because odd (length) key - True - >>> 'bb' in s # False because odd (length) key - False - >>> assert s.get('bb', None) == None - >>> len(s) - 2 - >>> list(s.keys()) - ['a', 'ccc'] - >>> list(s.values()) - [1, 'a string'] - >>> list(s.items()) - [('a', 1), ('ccc', 'a string')] - >>> s.get('a') - 1 - >>> assert s.get('bb') is None - >>> s['x'] = 10 - >>> list(s.items()) - [('a', 1), ('ccc', 'a string'), ('x', 10)] - >>> try: - ... s['xx'] = 'not an odd key' - ... raise ValueError("This should have failed") - ... except KeyError: - ... pass - """ - - from warnings import warn - - warn("""filtered_iter is on it's way to be deprecated. Use filt_iter instead. - To do so, replace: - - imports of filtered_iter by filt_iter - - non-keyword arguments by explicitly using arg names, for instance: - ```filtered_iter(lambda x: True) -> filt_iter(filt=lambda x: True)``` - """) - if store is None: - if not callable(filt): # if filt is not a callable... - # ... assume it's the collection of keys you want and make a filter function to filter those "in". - assert next( - iter(filt) - ), "filt should be a callable, or an iterable" - keys_that_should_be_filtered_in = set(filt) - - def filt(k): - return k in keys_that_should_be_filtered_in - - def wrap(store, name=name, __module__=__module__): - if not isinstance( - store, type - ): # then consider it to be an instance - store_instance = store - WrapperStore = filtered_iter(filt, name=name, __module__=__module__)(Store) - return WrapperStore(store_instance) - else: # it's a class we're wrapping - collection_cls = store - __module__ = __module__ or getattr(collection_cls, "__module__", None) - - name = name or "Filtered" + get_class_name(collection_cls) - wrapped_cls = type(name, (collection_cls,), {}) - - def __iter__(self): - yield from filter( - filt, super(wrapped_cls, self).__iter__() - ) - - wrapped_cls.__iter__ = __iter__ - - _define_keys_values_and_items_according_to_iter(wrapped_cls) - - def __len__(self): - c = 0 - for _ in self.__iter__(): - c += 1 - return c - - wrapped_cls.__len__ = __len__ - - def __contains__(self, k): - if filt(k): - return super(wrapped_cls, self).__contains__(k) - else: - return False - - wrapped_cls.__contains__ = __contains__ - - if hasattr(wrapped_cls, "__getitem__"): - - def __getitem__(self, k): - if filt(k): - return super(wrapped_cls, self).__getitem__(k) - else: - raise KeyError(f"Key not in store: {k}") - - wrapped_cls.__getitem__ = __getitem__ - - if hasattr(wrapped_cls, "get"): - - def get(self, k, default=None): - if filt(k): - return super(wrapped_cls, self).get(k, default) - else: - return default - - wrapped_cls.get = get - - if hasattr(wrapped_cls, "__setitem__"): - - def __setitem__(self, k, v): - if filt(k): - return super(wrapped_cls, self).__setitem__(k, v) - else: - raise KeyError(f"Key not in store: {k}") - - wrapped_cls.__setitem__ = __setitem__ - - if hasattr(wrapped_cls, "__delitem__"): - - def __delitem__(self, k): - if filt(k): - return super(wrapped_cls, self).__delitem__(k) - else: - raise KeyError(f"Key not in store: {k}") - - wrapped_cls.__delitem__ = __delitem__ - - if __module__ is not None: - wrapped_cls.__module__ = __module__ - - if hasattr(collection_cls, '__doc__'): - wrapped_cls.__doc__ = collection_cls.__doc__ - - return wrapped_cls - - return wrap - else: - return filtered_iter( - filt, store=None, name=name, __module__=__module__ - )(store)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/util.html b/docs/_modules/py2store/util.html deleted file mode 100644 index 6ecc169..0000000 --- a/docs/_modules/py2store/util.html +++ /dev/null @@ -1,1093 +0,0 @@ - - - - - - - - py2store.util — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.util

-import os
-import shutil
-import re
-from collections import namedtuple, defaultdict
-from warnings import warn
-from typing import Any, Hashable, Callable, Iterable, Optional
-# from functools import update_wrapper as _update_wrapper
-# from functools import wraps as _wraps
-from functools import partialmethod
-import functools
-
-# monkey patching WRAPPER_ASSIGNMENTS to get "proper" wrapping (adding defaults and kwdefaults
-
-wrapper_assignments = (
-    '__module__', '__name__', '__qualname__', '__doc__',
-    '__annotations__', '__defaults__', '__kwdefaults__')
-
-update_wrapper = functools.update_wrapper
-update_wrapper.__defaults__ = (functools.WRAPPER_ASSIGNMENTS, functools.WRAPPER_UPDATES)
-wraps = functools.wraps
-wraps.__defaults__ = (functools.WRAPPER_ASSIGNMENTS, functools.WRAPPER_UPDATES)
-
-
-# @_wraps(_wraps)
-# def wraps(wrapped, *args, **kwargs):
-#     _wrapped = _wraps(wrapped, *args, **kwargs)
-#     for attr
-
-
-
[docs]def partialclass(cls, *args, **kwargs): - """What partial(cls, *args, **kwargs) does, but returning a class instead of an object. - - :param cls: Class to get the partial of - :param kwargs: The kwargs to fix - - The raison d'être of partialclass is that it returns a type, so let's have a look at that with - a useless class. - - >>> class A: - ... pass - >>> assert isinstance(A, type) == isinstance(partialclass(A), type) == True - - >>> class A: - ... def __init__(self, a=0, b=1): - ... self.a, self.b = a, b - ... def mysum(self): - ... return self.a + self.b - ... def __repr__(self): - ... return f"{self.__class__.__name__}(a={self.a}, b={self.b})" - >>> - >>> assert isinstance(A, type) == isinstance(partialclass(A), type) == True - >>> - >>> assert str(signature(A)) == '(a=0, b=1)' - >>> - >>> a = A() - >>> assert a.mysum() == 1 - >>> assert str(a) == 'A(a=0, b=1)' - >>> - >>> assert A(a=10).mysum() == 11 - >>> assert str(A()) == 'A(a=0, b=1)' - >>> - >>> - >>> AA = partialclass(A, b=2) - >>> assert str(signature(AA)) == '(a=0, *, b=2)' - >>> aa = AA() - >>> assert aa.mysum() == 2 - >>> assert str(aa) == 'A(a=0, b=2)' - >>> assert AA(a=1, b=3).mysum() == 4 - >>> assert str(AA(3)) == 'A(a=3, b=2)' - >>> - >>> AA = partialclass(A, a=7) - >>> assert str(signature(AA)) == '(*, a=7, b=1)' - >>> assert AA().mysum() == 8 - >>> assert str(AA(a=3)) == 'A(a=3, b=1)' - - Note in the last partial that since ``a`` was fixed, you need to specify the keyword ``AA(a=3)``. - ``AA(3)`` won't work: - - >>> AA(3) - Traceback (most recent call last): - ... - TypeError: __init__() got multiple values for argument 'a' - - On the other hand, you can use *args to specify the fixtures: - - >>> AA = partialclass(A, 22) - >>> assert str(AA()) == 'A(a=22, b=1)' - >>> assert str(signature(AA)) == '(b=1)' - >>> assert str(AA(3)) == 'A(a=22, b=3)' - - ``` - """ - assert isinstance(cls, type), f"cls should be a type, was a {type(cls)}: {cls}" - - class PartialClass(cls): - __init__ = partialmethod(cls.__init__, *args, **kwargs) - - copy_attrs(PartialClass, cls, attrs=('__name__', '__qualname__', '__module__', '__doc__')) - - return PartialClass
- - -
[docs]def copy_attrs(target, source, attrs, raise_error_if_an_attr_is_missing=True): - """Copy attributes from one object to another. - - >>> class A: - ... x = 0 - >>> class B: - ... x = 1 - ... yy = 2 - ... zzz = 3 - >>> dict_of = lambda o: {a: getattr(o, a) for a in dir(A) if not a.startswith('_')} - >>> dict_of(A) - {'x': 0} - >>> copy_attrs(A, B, 'yy') - >>> dict_of(A) - {'x': 0, 'yy': 2} - >>> copy_attrs(A, B, ['x', 'zzz']) - >>> dict_of(A) - {'x': 1, 'yy': 2, 'zzz': 3} - - But if you try to copy something that `B` (the source) doesn't have, copy_attrs will complain: - >>> copy_attrs(A, B, 'this_is_not_an_attr') - Traceback (most recent call last): - ... - AttributeError: type object 'B' has no attribute 'this_is_not_an_attr' - - If you tell it not to complain, it'll just ignore attributes that are not in source. - >>> copy_attrs(A, B, ['nothing', 'here', 'exists'], raise_error_if_an_attr_is_missing=False) - >>> dict_of(A) - {'x': 1, 'yy': 2, 'zzz': 3} - """ - if isinstance(attrs, str): - attrs = (attrs,) - if raise_error_if_an_attr_is_missing: - filt = lambda a: True - else: - filt = lambda a: hasattr(source, a) - for a in filter(filt, attrs): - setattr(target, a, getattr(source, a))
- - -def copy_attrs_from(from_obj, to_obj, attrs): - from warnings import warn - - warn(f"Deprecated. Use copy_attrs instead.", DeprecationWarning) - copy_attrs(to_obj, from_obj, attrs) - return to_obj - - -
[docs]def norm_kv_filt(kv_filt: Callable[[Any], bool]): - """Prepare a boolean function to be used with `filter` when fed an iterable of (k, v) pairs. - - So you have a mapping. Say a dict `d`. Now you want to go through d.items(), - filtering based on the keys, or the values, or both. - - It's not hard to do, really. If you're using a dict you might use a dict comprehension, - or in the general case you might do a `filter(lambda kv: my_filt(kv[0], kv[1]), d.items())` - if you have a `my_filt` that works wiith k and v, etc. - - But thought simple, it can become a bit muddled. - `norm_kv_filt` simplifies this by allowing you to bring your own filtering boolean function, - whether it's a key-based, value-based, or key-value-based one, and it will make a - ready-to-use with `filter` function for you. - - Only thing: Your function needs to call a key `k` and a value `v`. - But hey, it's alright, if you have a function that calls things differently, just do - something like - ``` - new_filt_func = lambda k, v: your_filt_func(..., key=k, ..., value=v, ...) - ``` - and all will be fine. - - :param kv_filt: callable (starting with signature (k), (v), or (k, v)), and returning a boolean - :return: A normalized callable. - - >>> d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} - >>> list(filter(norm_kv_filt(lambda k: k in {'b', 'd'}), d.items())) - [('b', 2), ('d', 4)] - >>> list(filter(norm_kv_filt(lambda v: v > 2), d.items())) - [('c', 3), ('d', 4)] - >>> list(filter(norm_kv_filt(lambda k, v: (v > 1) & (k != 'c')), d.items())) - [('b', 2), ('d', 4)] - """ - if kv_filt is None: - return None # because `filter` works with a callable, or None, so we align - - raise_msg = ( - f"kv_filt should be callable (starting with signature (k), (v), or (k, v))," - "and returning a boolean. What you gave me was {fv_filt}" - ) - assert callable(kv_filt), raise_msg - - params = list(signature(kv_filt).parameters.values()) - assert len(params), raise_msg - _kv_filt = kv_filt - if params[0].name == "v": - - def kv_filt(k, v): - return _kv_filt(v) - - elif params[0].name == "k": - if len(params) > 1: - if params[1].name != "v": - raise ValueError(raise_msg) - else: - - def kv_filt(k, v): - return _kv_filt(k) - - else: - raise ValueError(raise_msg) - - def __kv_filt(kv_item): - return kv_filt(*kv_item) - - __kv_filt.__name__ = kv_filt.__name__ - - return __kv_filt
- - -var_str_p = re.compile("\W|^(?=\d)") - -Item = Any - - -
[docs]def add_attrs(remember_added_attrs=True, if_attr_exists="raise", **attrs): - """Make a function that will add attributes to an obj. - Originally meant to be used as a decorator of a function, to inject - >>> from py2store.util import add_attrs - >>> @add_attrs(bar='bituate', hello='world') - ... def foo(): - ... pass - >>> [x for x in dir(foo) if not x.startswith('_')] - ['bar', 'hello'] - >>> foo.bar - 'bituate' - >>> foo.hello - 'world' - >>> foo._added_attrs # Another attr was added to hold the list of attributes added (in case we need to remove them - ['bar', 'hello'] - """ - - def add_attrs_to_func(obj): - attrs_added = [] - for attr_name, attr_val in attrs.items(): - if hasattr(obj, attr_name): - if if_attr_exists == "raise": - raise AttributeError( - f"Attribute {attr_name} already exists in {obj}" - ) - elif if_attr_exists == "warn": - warn(f"Attribute {attr_name} already exists in {obj}") - elif if_attr_exists == "skip": - continue - else: - raise ValueError( - f"Unknown value for if_attr_exists: {if_attr_exists}" - ) - setattr(obj, attr_name, attr_val) - attrs_added.append(attr_name) - - if remember_added_attrs: - obj._added_attrs = attrs_added - - return obj - - return add_attrs_to_func
- - -def fullpath(path): - if path.startswith('~'): - path = os.path.expanduser(path) - return os.path.abspath(path) - - -def attrs_of(obj): - return set(dir(obj)) - - -
[docs]def format_invocation(name="", args=(), kwargs=None): - """Given a name, positional arguments, and keyword arguments, format - a basic Python-style function call. - - >>> print(format_invocation('func', args=(1, 2), kwargs={'c': 3})) - func(1, 2, c=3) - >>> print(format_invocation('a_func', args=(1,))) - a_func(1) - >>> print(format_invocation('kw_func', kwargs=[('a', 1), ('b', 2)])) - kw_func(a=1, b=2) - - """ - kwargs = kwargs or {} - a_text = ", ".join([repr(a) for a in args]) - if isinstance(kwargs, dict): - kwarg_items = [(k, kwargs[k]) for k in sorted(kwargs)] - else: - kwarg_items = kwargs - kw_text = ", ".join(["%s=%r" % (k, v) for k, v in kwarg_items]) - - all_args_text = a_text - if all_args_text and kw_text: - all_args_text += ", " - all_args_text += kw_text - - return "%s(%s)" % (name, all_args_text)
- - -
[docs]def groupby( - items: Iterable[Item], - key: Callable[[Item], Hashable], - val: Optional[Callable[[Item], Any]] = None, - group_factory=list, -) -> dict: - """Groups items according to group keys updated from those items through the given (item_to_)key function. - - Args: - items: iterable of items - key: The function that computes a key from an item. Needs to return a hashable. - val: An optional function that computes a val from an item. If not given, the item itself will be taken. - group_factory: The function to make new (empty) group objects and accumulate group items. - group_items = group_collector() will be called to make a new empty group collection - group_items.append(x) will be called to add x to that collection - The default is `list` - - Returns: A dict of {group_key: items_in_that_group, ...} - - >>> groupby(range(11), key=lambda x: x % 3) - {0: [0, 3, 6, 9], 1: [1, 4, 7, 10], 2: [2, 5, 8]} - >>> - >>> tokens = ['the', 'fox', 'is', 'in', 'a', 'box'] - >>> groupby(tokens, len) - {3: ['the', 'fox', 'box'], 2: ['is', 'in'], 1: ['a']} - >>> key_map = {1: 'one', 2: 'two'} - >>> groupby(tokens, lambda x: key_map.get(len(x), 'more')) - {'more': ['the', 'fox', 'box'], 'two': ['is', 'in'], 'one': ['a']} - >>> stopwords = {'the', 'in', 'a', 'on'} - >>> groupby(tokens, lambda w: w in stopwords) - {True: ['the', 'in', 'a'], False: ['fox', 'is', 'box']} - >>> groupby(tokens, lambda w: ['words', 'stopwords'][int(w in stopwords)]) - {'stopwords': ['the', 'in', 'a'], 'words': ['fox', 'is', 'box']} - """ - groups = defaultdict(group_factory) - if val is None: - for item in items: - groups[key(item)].append(item) - else: - for item in items: - groups[key(item)].append(val(item)) - return dict(groups)
- - -
[docs]def regroupby(items, *key_funcs, **named_key_funcs): - """REcursive groupby. Applies the groupby function recursively, using a sequence of key functions. - - Note: The named_key_funcs argument names don't have any external effect. - They just give a name to the key function, for code reading clarity purposes. - - >>> # group by how big the number is, then by it's mod 3 value - >>> # note that named_key_funcs argument names doesn't have any external effect (but give a name to the function) - >>> regroupby([1, 2, 3, 4, 5, 6, 7], lambda x: 'big' if x > 5 else 'small', mod3=lambda x: x % 3) - {'small': {1: [1, 4], 2: [2, 5], 0: [3]}, 'big': {0: [6], 1: [7]}} - >>> - >>> tokens = ['the', 'fox', 'is', 'in', 'a', 'box'] - >>> stopwords = {'the', 'in', 'a', 'on'} - >>> word_category = lambda x: 'stopwords' if x in stopwords else 'words' - >>> regroupby(tokens, word_category, len) - {'stopwords': {3: ['the'], 2: ['in'], 1: ['a']}, 'words': {3: ['fox', 'box'], 2: ['is']}} - >>> regroupby(tokens, len, word_category) - {3: {'stopwords': ['the'], 'words': ['fox', 'box']}, 2: {'words': ['is'], 'stopwords': ['in']}, 1: {'stopwords': ['a']}} - """ - key_funcs = list(key_funcs) + list(named_key_funcs.values()) - assert len(key_funcs) > 0, "You need to have at least one key_func" - if len(key_funcs) == 1: - return groupby(items, key=key_funcs[0]) - else: - key_func, *key_funcs = key_funcs - groups = groupby(items, key=key_func) - return { - group_key: regroupby(group_items, *key_funcs) - for group_key, group_items in groups.items() - }
- - -GroupItems = Iterable[Item] -from inspect import signature - - -
[docs]def igroupby( - items: Iterable[Item], - key: Callable[[Item], Hashable], - val: Optional[Callable[[Item], Any]] = None, - group_factory: Callable[[], GroupItems] = list, - group_release_cond: Callable[[Any, Any], bool] = lambda k, v: False, - release_remainding=True, - append_to_group_items: Callable[[GroupItems, Item], Any] = list.append -) -> dict: - """The generator version of py2store groupby. - Groups items according to group keys updated from those items through the given (item_to_)key function, - yielding the groups according to a logic defined by ``group_release_cond`` - - Args: - items: iterable of items - key: The function that computes a key from an item. Needs to return a hashable. - val: An optional function that computes a val from an item. If not given, the item itself will be taken. - group_factory: The function to make new (empty) group objects and accumulate group items. - group_items = group_collector() will be called to make a new empty group collection - group_items.append(x) will be called to add x to that collection - The default is `list` - group_release_cond: A boolean function that will be applied, at every iteration, - to the accumulated items of the group that was just updated, - and determines (if True) if the (group_key, group_items) should be yielded. - The default is False, which results in - ``lambda group_key, group_items: False`` being used. - release_remainding: Once the input items have been consumed, there may still be some - items in the grouping "cache". ``release_remainding`` is a boolean that indicates whether - the contents of this cache should be released or not. - - Yields: ``(group_key, items_in_that_group)`` pairs - - - The following will group numbers according to their parity (0 for even, 1 for odd), - releasing a list of numbers collected when that list reaches length 3: - - >>> g = igroupby(items=range(11), - ... key=lambda x: x % 2, - ... group_release_cond=lambda k, v: len(v) == 3) - >>> list(g) - [(0, [0, 2, 4]), (1, [1, 3, 5]), (0, [6, 8, 10]), (1, [7, 9])] - - If we specify ``release_remainding=False`` though, we won't get - >>> g = igroupby(items=range(11), - ... key=lambda x: x % 2, - ... group_release_cond=lambda k, v: len(v) == 3, - ... release_remainding=False) - >>> list(g) - [(0, [0, 2, 4]), (1, [1, 3, 5]), (0, [6, 8, 10])] - - # >>> grps = partial(igroupby, group_release_cond=False, release_remainding=True) - - - Below we show that, with the default ``group_release_cond = lambda k, v: False`` - and release_remainding=True`` we have ``dict(igroupby(...)) == groupby(...)`` - - >>> from functools import partial - >>> from py2store import groupby - >>> - >>> kws = dict(items=range(11), key=lambda x: x % 3) - >>> assert (dict(igroupby(**kws)) == groupby(**kws) - ... == {0: [0, 3, 6, 9], 1: [1, 4, 7, 10], 2: [2, 5, 8]}) - >>> - >>> tokens = ['the', 'fox', 'is', 'in', 'a', 'box'] - >>> kws = dict(items=tokens, key=len) - >>> assert (dict(igroupby(**kws)) == groupby(**kws) - ... == {3: ['the', 'fox', 'box'], 2: ['is', 'in'], 1: ['a']}) - >>> - >>> key_map = {1: 'one', 2: 'two'} - >>> kws.update(key=lambda x: key_map.get(len(x), 'more')) - >>> assert (dict(igroupby(**kws)) == groupby(**kws) - ... == {'more': ['the', 'fox', 'box'], 'two': ['is', 'in'], 'one': ['a']}) - >>> - >>> stopwords = {'the', 'in', 'a', 'on'} - >>> kws.update(key=lambda w: w in stopwords) - >>> assert (dict(igroupby(**kws)) == groupby(**kws) - ... == {True: ['the', 'in', 'a'], False: ['fox', 'is', 'box']}) - >>> kws.update(key=lambda w: ['words', 'stopwords'][int(w in stopwords)]) - >>> assert (dict(igroupby(**kws)) == groupby(**kws) - ... == {'stopwords': ['the', 'in', 'a'], 'words': ['fox', 'is', 'box']}) - - """ - groups = defaultdict(group_factory) - - assert callable(group_release_cond), ( - "group_release_cond should be callable (filter boolean function) or False. " - f"Was {group_release_cond}") - assert len(signature(group_release_cond).parameters) == 2, ( - "group_release_cond should take two inputs: The group_key and the group_items.\n" - f"The arguments of the function you gave me are: {signature(group_release_cond)}" - ) - for item in items: - group_key = key(item) - group_items = groups[group_key] - if val is None: - append_to_group_items(group_items, item) - else: - append_to_group_items(group_items, val(item)) - if group_release_cond(group_key, group_items): - yield group_key, group_items - del groups[group_key] - - if release_remainding: - for group_key, group_items in groups.items(): - yield group_key, group_items
- - -def ntup(**kwargs): - return namedtuple("NamedTuple", list(kwargs))(**kwargs) - - -
[docs]def str_to_var_str(s: str) -> str: - """Make a valid python variable string from the input string. - Left untouched if already valid. - - >>> str_to_var_str('this_is_a_valid_var_name') - 'this_is_a_valid_var_name' - >>> str_to_var_str('not valid #)*(&434') - 'not_valid_______434' - >>> str_to_var_str('99_ballons') - '_99_ballons' - """ - return var_str_p.sub("_", s)
- - -
[docs]def fill_with_dflts(d, dflt_dict=None): - """ - Fed up with multiline handling of dict arguments? - Fed up of repeating the if d is None: d = {} lines ad nauseam (because defaults can't be dicts as a default - because dicts are mutable blah blah, and the python kings don't seem to think a mutable dict is useful enough)? - Well, my favorite solution would be a built-in handling of the problem of complex/smart defaults, - that is visible in the code and in the docs. But for now, here's one of the tricks I use. - - Main use is to handle defaults of function arguments. Say you have a function `func(d=None)` and you want - `d` to be a dict that has at least the keys `foo` and `bar` with default values 7 and 42 respectively. - Then, in the beginning of your function code you'll say: - - d = fill_with_dflts(d, {'a': 7, 'b': 42}) - - See examples to know how to use it. - - ATTENTION: A shallow copy of the dict is made. Know how that affects you (or not). - ATTENTION: This is not recursive: It won't be filling any nested fields with defaults. - - Args: - d: The dict you want to "fill" - dflt_dict: What to fill it with (a {k: v, ...} dict where if k is missing in d, you'll get a new field k, with - value v. - - Returns: - a dict with the new key:val entries (if the key was missing in d). - - >>> fill_with_dflts(None) - {} - >>> fill_with_dflts(None, {'a': 7, 'b': 42}) - {'a': 7, 'b': 42} - >>> fill_with_dflts({}, {'a': 7, 'b': 42}) - {'a': 7, 'b': 42} - >>> fill_with_dflts({'b': 1000}, {'a': 7, 'b': 42}) - {'a': 7, 'b': 1000} - """ - if d is None: - d = {} - if dflt_dict is None: - dflt_dict = {} - return dict(dflt_dict, **d)
- - -# Note: Had replaced with cached_property (new in 3.8) -# if not sys.version_info >= (3, 8): -# from functools import cached_property -# # etc... -# But then I realized that the way cached_property is implemented, pycharm does not see the properties (lint) -# So I'm reverting to lazyprop -# TODO: Keep track of the evolution of functools.cached_property and compare performance. -
[docs]class lazyprop: - """ - A descriptor implementation of lazyprop (cached property). - Made based on David Beazley's "Python Cookbook" book and enhanced with boltons.cacheutils ideas. - - >>> class Test: - ... def __init__(self, a): - ... self.a = a - ... @lazyprop - ... def len(self): - ... print('generating "len"') - ... return len(self.a) - >>> t = Test([0, 1, 2, 3, 4]) - >>> t.__dict__ - {'a': [0, 1, 2, 3, 4]} - >>> t.len - generating "len" - 5 - >>> t.__dict__ - {'a': [0, 1, 2, 3, 4], 'len': 5} - >>> t.len - 5 - >>> # But careful when using lazyprop that no one will change the value of a without deleting the property first - >>> t.a = [0, 1, 2] # if we change a... - >>> t.len # ... we still get the old cached value of len - 5 - >>> del t.len # if we delete the len prop - >>> t.len # ... then len being recomputed again - generating "len" - 3 - """ - - def __init__(self, func): - self.__doc__ = getattr(func, "__doc__") - self.__isabstractmethod__ = getattr( - func, "__isabstractmethod__", False - ) - self.func = func - - def __get__(self, instance, cls): - if instance is None: - return self - else: - value = instance.__dict__[self.func.__name__] = self.func(instance) - return value - - def __repr__(self): - cn = self.__class__.__name__ - return "<%s func=%s>" % (cn, self.func)
- - -from functools import lru_cache, wraps -import weakref - - -@wraps(lru_cache) -def memoized_method(*lru_args, **lru_kwargs): - def decorator(func): - @wraps(func) - def wrapped_func(self, *args, **kwargs): - # Storing the wrapped method inside the instance since a strong reference to self would not allow it to die. - self_weak = weakref.ref(self) - - @wraps(func) - @lru_cache(*lru_args, **lru_kwargs) - def cached_method(*args, **kwargs): - return func(self_weak(), *args, **kwargs) - - setattr(self, func.__name__, cached_method) - return cached_method(*args, **kwargs) - - return wrapped_func - - return decorator - - -
[docs]class lazyprop_w_sentinel(lazyprop): - """ - A descriptor implementation of lazyprop (cached property). - Inserts a `self.func.__name__ + '__cache_active'` attribute - - >>> class Test: - ... def __init__(self, a): - ... self.a = a - ... @lazyprop_w_sentinel - ... def len(self): - ... print('generating "len"') - ... return len(self.a) - >>> t = Test([0, 1, 2, 3, 4]) - >>> lazyprop_w_sentinel.cache_is_active(t, 'len') - False - >>> t.__dict__ # let's look under the hood - {'a': [0, 1, 2, 3, 4]} - >>> t.len - generating "len" - 5 - >>> lazyprop_w_sentinel.cache_is_active(t, 'len') - True - >>> t.len # notice there's no 'generating "len"' print this time! - 5 - >>> t.__dict__ # let's look under the hood - {'a': [0, 1, 2, 3, 4], 'len': 5, 'sentinel_of__len': True} - >>> # But careful when using lazyprop that no one will change the value of a without deleting the property first - >>> t.a = [0, 1, 2] # if we change a... - >>> t.len # ... we still get the old cached value of len - 5 - >>> del t.len # if we delete the len prop - >>> t.len # ... then len being recomputed again - generating "len" - 3 - """ - - sentinel_prefix = "sentinel_of__" - - def __get__(self, instance, cls): - if instance is None: - return self - else: - value = instance.__dict__[self.func.__name__] = self.func(instance) - setattr( - instance, self.sentinel_prefix + self.func.__name__, True - ) # my hack - return value - - @classmethod - def cache_is_active(cls, instance, attr): - return getattr(instance, cls.sentinel_prefix + attr, False)
- - -class Struct: - def __init__(self, **attr_val_dict): - for attr, val in attr_val_dict.items(): - setattr(self, attr, val) - - -class MutableStruct(Struct): - def extend(self, **attr_val_dict): - for attr in attr_val_dict.keys(): - if hasattr(self, attr): - raise AttributeError( - f"The attribute {attr} already exists. Delete it if you want to reuse it!" - ) - for attr, val in attr_val_dict.items(): - setattr(self, attr, val) - - -
[docs]def max_common_prefix(a): - """ - Given a list of strings (or other sliceable sequences), returns the longest common prefix - :param a: list-like of strings - :return: the smallest common prefix of all strings in a - """ - if not a: - return "" - # Note: Try to optimize by using a min_max function to give me both in one pass. The current version is still faster - s1 = min(a) - s2 = max(a) - for i, c in enumerate(s1): - if c != s2[i]: - return s1[:i] - return s1
- - -class SimpleProperty(object): - def __get__(self, obj, objtype=None): - return obj.d - - def __set__(self, obj, value): - obj.d = value - - def __delete__(self, obj): - del obj.d - - -class DelegatedAttribute: - def __init__(self, delegate_name, attr_name): - self.attr_name = attr_name - self.delegate_name = delegate_name - - def __get__(self, instance, owner): - if instance is None: - return self - else: - # return instance.delegate.attr - return getattr(self.delegate(instance), self.attr_name) - - def __set__(self, instance, value): - # instance.delegate.attr = value - setattr(self.delegate(instance), self.attr_name, value) - - def __delete__(self, instance): - delattr(self.delegate(instance), self.attr_name) - - def delegate(self, instance): - return getattr(instance, self.delegate_name) - - def __str__(self): - return "" - - # def __call__(self, instance, *args, **kwargs): - # return self.delegate(instance)(*args, **kwargs) - - -def delegate_as( - delegate_cls, to="delegate", include=frozenset(), exclude=frozenset() -): - raise NotImplementedError("Didn't manage to make this work fully") - # turn include and ignore into sets, if they aren't already - include = set(include) - exclude = set(exclude) - delegate_attrs = set(delegate_cls.__dict__.keys()) - attributes = include | delegate_attrs - exclude - - def inner(cls): - # create property for storing the delegate - setattr(cls, to, property()) - # don't bother adding attributes that the class already has - attrs = attributes - set(cls.__dict__.keys()) - # set all the attributes - for attr in attrs: - setattr(cls, attr, DelegatedAttribute(to, attr)) - return cls - - return inner - - -class HashableMixin: - def __hash__(self): - return id(self) - - -class ImmutableMixin: - def _immutable(self, *args, **kws): - raise TypeError("object is immutable") - - __setitem__ = _immutable - __delitem__ = _immutable - clear = _immutable - update = _immutable - setdefault = _immutable - pop = _immutable - popitem = _immutable - - -
[docs]class imdict(dict, HashableMixin, ImmutableMixin): - """ A frozen hashable dict """ - - pass
- - -def move_files_of_folder_to_trash(folder): - trash_dir = os.path.join( - os.getenv("HOME"), ".Trash" - ) # works with mac (perhaps linux too?) - assert os.path.isdir(trash_dir), f"{trash_dir} directory not found" - - for f in os.listdir(folder): - src = os.path.join(folder, f) - if os.path.isfile(src): - dst = os.path.join(trash_dir, f) - print(f"Moving to trash: {src}") - shutil.move(src, dst) - - -class ModuleNotFoundErrorNiceMessage: - def __init__(self, msg=None): - self.msg = msg - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_type is ModuleNotFoundError: - if self.msg is not None: - warn(self.msg) - else: - raise ModuleNotFoundError( - f""" -It seems you don't have required `{exc_val.name}` package for this Store. -Try installing it by running: - - pip install {exc_val.name} - -in your terminal. -For more information: https://pypi.org/project/{exc_val.name} - """ - ) - - -class ModuleNotFoundWarning: - def __init__(self, msg="It seems you don't have a required package."): - self.msg = msg - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_type is ModuleNotFoundError: - warn(self.msg) - # if exc_val is not None and getattr(exc_val, 'name', None) is not None: - # warn(f""" - # It seems you don't have required `{exc_val.name}` package for this Store. - # This is just a warning: The process goes on... - # (But, hey, if you really need that package, try installing it by running: - # - # pip install {exc_val.name} - # - # in your terminal. - # For more information: https://pypi.org/project/{exc_val.name}, or google around... - # """) - # else: - # print("It seems you don't have a required package") - return True - - -class ModuleNotFoundIgnore: - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_type is ModuleNotFoundError: - pass - return True - - -def num_of_args(func): - return len(signature(func).parameters) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/affine_conversion.html b/docs/_modules/py2store/utils/affine_conversion.html deleted file mode 100644 index d2b7957..0000000 --- a/docs/_modules/py2store/utils/affine_conversion.html +++ /dev/null @@ -1,328 +0,0 @@ - - - - - - - - py2store.utils.affine_conversion — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.affine_conversion

-"""
-utils to carry out affine transformations (of indices)
-"""
-
-
-
[docs]class AffineConverter(object): - """ - Getting a callable that will perform an affine conversion. - Note, it does it as - (val - offset) * scale - (Note slope-intercept style (though there is the .from_slope_and_intercept constructor method for that) - - Inverse is available through the inv method, performing: - val / scale + offset - - >>> convert = AffineConverter(scale=0.5, offset=1) - >>> convert(0) - -0.5 - >>> convert(10) - 4.5 - >>> convert.inv(4) - 9.0 - >>> convert.inv(4.5) - 10.0 - """ - - def __init__(self, scale=1.0, offset=0.0): - self.scale = scale - self.offset = offset - - @classmethod - def from_slope_and_intercept(cls, slope=1.0, intercept=0.0): - cls(offset=-intercept / slope, scale=slope) - - def __call__(self, x): - return (x - self.offset) * self.scale - - def inv(self, x): - return x / self.scale + self.offset - - def map(self, seq): - return (self(x) for x in seq) - - def invmap(self, seq): - return (self.inv(x) for x in seq)
- - -
[docs]def get_affine_converter_and_inverse( - scale=1, offset=0, source_type_cast=None, target_type_cast=None -): - """ - Getting two affine functions with given scale and offset, that are inverse of each other. Namely (for input val): - (val - offset) * scale and val / scale + offset - Note this is not "slope intercept" style!! - - The source_type_cast and target_type_case (optional), allow the user to specify if these transformations need to - be further cast to a given type. - :param scale: - :param offset: - :param source_type_cast: function to apply to input - :param target_type_cast: function to apply to output - :return: Two single val functions: affine_converter, inverse_affine_converter - - Note: Code is a lot more complex than the basic operations it performs. The reason was a worry of efficiency since - the functions that are returned are intended to be used in long loops. - - See also: ocore.utils.conversion.AffineConverter - - >>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1) - >>> affine_converter(0) - -0.5 - >>> affine_converter(10) - 4.5 - >>> inverse_affine_converter(4) - 9.0 - >>> inverse_affine_converter(4.5) - 10.0 - >>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1,target_type_cast=int) - >>> affine_converter(10) - 4 - """ - if offset != 0: - if scale != 1: - if target_type_cast is None: - - def affine_converter(val): - return (val - offset) * scale - - else: - - def affine_converter(val): - return target_type_cast((val - offset) * scale) - - if source_type_cast is None: - - def inverse_affine_converter(val): - return val / scale + offset - - else: - - def inverse_affine_converter(val): - return source_type_cast(val / scale + offset) - - else: # scale 1, so can be ignored - if target_type_cast is None: - - def affine_converter(val): - return val - offset - - else: - - def affine_converter(val): - return target_type_cast(val - offset) - - if source_type_cast is None: - - def inverse_affine_converter(val): - return val + offset - - else: - - def inverse_affine_converter(val): - return source_type_cast(val + offset) - - else: # no offset - if target_type_cast is None: - - def affine_converter(val): - return scale * val - - else: - - def affine_converter(val): - return target_type_cast(scale * val) - - if source_type_cast is None: - - def inverse_affine_converter(val): - return val / scale - - else: - - def inverse_affine_converter(val): - return source_type_cast(val / scale) - - return affine_converter, inverse_affine_converter
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/appendable.html b/docs/_modules/py2store/utils/appendable.html deleted file mode 100644 index a190fa3..0000000 --- a/docs/_modules/py2store/utils/appendable.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - py2store.utils.appendable — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.appendable

-"""
-Tools to add append-functionality to key-val stores. The main function is
-    `appendable_store_cls = add_append_functionality_to_store_cls(store_cls, item2kv, ...)`
-You give it the `store_cls` you want to sub class, and a item -> (key, val) function, and you get a store (subclass) that
-has a `store.append(item)` method. Also includes an extend method (that just called appends in a loop.
-
-See add_append_functionality_to_store_cls docs for examples.
-"""
-
-import time
-import types
-
-
-
[docs]def define_extend_as_seq_of_appends(obj): - """Inject an extend method in obj that will used append method. - - Args: - obj: Class (type) or instance of an object that has an "append" method. - - Returns: The obj, but with that extend method. - - >>> class A: - ... def __init__(self): - ... self.t = list() - ... def append(self, item): - ... self.t.append(item) - ... - >>> AA = define_extend_as_seq_of_appends(A) - >>> a = AA() - >>> a.extend([1,2,3]) - >>> a.t - [1, 2, 3] - >>> a.extend([10, 20]) - >>> a.t - [1, 2, 3, 10, 20] - >>> a = A() - >>> a = define_extend_as_seq_of_appends(a) - >>> a.extend([1,2,3]) - >>> a.t - [1, 2, 3] - >>> a.extend([10, 20]) - >>> a.t - [1, 2, 3, 10, 20] - - """ - assert hasattr( - obj, "append" - ), f"Your object needs to have an append method! Object was: {obj}" - - def extend(self, items): - for item in items: - self.append(item) - - if isinstance(obj, type): - obj = type(obj.__name__, (obj,), {}) - obj.extend = extend - else: - obj.extend = types.MethodType(extend, obj) - return obj
- - -
[docs]def add_append_functionality_to_store_cls( - store_cls, item2kv, new_store_name=None -): - """Makes a new class with append (and consequential extend) methods - - - Args: - store_cls: The store class to subclass - item2kv: The function that produces a (key, val) pair from an item - new_store_name: The name to give the new class (default will be 'Appendable' + store_cls.__name__) - - Returns: A subclass of store_cls with two additional methods: append, and extend. - - - >>> item_to_kv = lambda item: (item['L'], item) # use value of 'L' as the key, and value is the item itself - >>> MyStore = add_append_functionality_to_store_cls(dict, item_to_kv) - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [('let', {'L': 'let', 'I': 'it', 'G': 'go'})] - >>> # Use mk_item2kv.from_item_to_key_params_and_val with tuple key params - ... item_to_kv = mk_item2kv_for.item_to_key_params_and_val(lambda x: ((x['L'], x['I']), x['G']), '{}/{}') - >>> MyStore = add_append_functionality_to_store_cls(dict, item_to_kv) - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [('let/it', 'go')] - >>> # Use mk_item2kv.from_item_to_key_params_and_val with dict key params - ... item_to_kv = mk_item2kv_for.item_to_key_params_and_val( - ... lambda x: ({'L': x['L'], 'G': x['G']}, x['I']), '{G}_{L}') - >>> MyStore = add_append_functionality_to_store_cls(dict, item_to_kv) - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [('go_let', 'it')] - >>> # Use mk_item2kv.fields to get a tuple key from item fields, defining the sub-dict of the remaining fields to be the value - ... item_to_kv = mk_item2kv_for.fields(['G', 'L'], key_as_tuple=True) - >>> MyStore = add_append_functionality_to_store_cls(dict, item_to_kv) - >>> s = MyStore(); s.append({'L': 'let', 'I': 'it', 'G': 'go'}); list(s.items()) - [(('go', 'let'), {'I': 'it'})] - """ - - new_store_name = new_store_name or ("Appendable" + store_cls.__name__) - - def append(self, item): - k, v = item2kv(item) - self[k] = v - - def extend(self, items): - for item in items: - self.append(item) - - return type( - new_store_name, (store_cls,), {"append": append, "extend": extend} - )
- - -######################################################################################################################## - - -
[docs]class mk_item2kv_for: - """A bunch of functions to make item2kv functions - - A few examples (see individual methods' docs for more examples) - - >>> # item_to_key - >>> item2kv = mk_item2kv_for.item_to_key(item2key=lambda item: item['L'] ) - >>> item2kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let', {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> - >>> # utc_key - >>> import time - >>> item2key = mk_item2kv_for.utc_key() - >>> k, v = item2key('some data') - >>> assert abs(time.time() - k) < 0.01 # which asserts that k is indeed a (current) utc timestamp - >>> assert v == 'some data' # just the item itself - >>> - >>> # item_to_key_params_and_val - >>> item_to_kv = mk_item2kv_for.item_to_key_params_and_val(lambda x: ((x['L'], x['I']), x['G']), '{}/{}') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let/it', 'go') - >>> - >>> # fields - >>> item_to_kv = mk_item2kv_for.fields(['L', 'I']) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ({'L': 'let', 'I': 'it'}, {'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), keep_field_in_value=True) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) # note the order of the key is not ('G', 'L')... - ({'L': 'let', 'G': 'go'}, {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), key_as_tuple=True) # but ('G', 'L') order is respected here - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - (('go', 'let'), {'I': 'it'}) - """ - -
[docs] @staticmethod - def item_to_key(item2key): - """Make item2kv from a item2key function (the value will be the item itself). - - Args: - item2key: an item -> key function - - Returns: an item -> (key, val) function - - >>> item2key = lambda item: item['L'] # use value of 'L' as the key - >>> item2key({'L': 'let', 'I': 'it', 'G': 'go'}) - 'let' - >>> item2kv = mk_item2kv_for.item_to_key(item2key) - >>> item2kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let', {'L': 'let', 'I': 'it', 'G': 'go'}) - """ - - def item2kv(item): - return item2key(item), item - - return item2kv
- -
[docs] @staticmethod - def utc_key(offset_s=0.0): - """Make an item2kv function that uses the current time as the key, and the unchanged item as a value. - The offset_s, which is added to the output key, can be used, for example, to align to another system's clock, - or to get a more accurate timestamp of an event. - - Use case for offset_s: - * Align to another system's clock - * Get more accurate timestamping of an event. For example, in situations where the item is a chunk of live - streaming data and we want the key (timestamp) to represent the timestamp of the beginning of the chunk. - Without an offset_s, the timestamp would be the timestamp after the last byte of the chunk was produced, - plus the time it took to reach the present function. If we know the data production rate (e.g. sample rate) - and the average lag to get to the present function, we can get a more accurate timestamp for the beginning - of the chunk - - Args: - offset_s: An offset (in seconds, possibly negative) to add to the current time. - - Returns: an item -> (current_utc_s, item) function - - >>> import time - >>> item2key = mk_item2kv_for.utc_key() - >>> k, v = item2key('some data') - >>> assert abs(time.time() - k) < 0.01 # which asserts that k is indeed a (current) utc timestamp - >>> assert v == 'some data' # just the item itself - - """ - if ( - offset_s == 0.0 - ): # splitting for extra speed (important in real time apps) - - def item2kv(item): - return time.time(), item - - else: - - def item2kv(item): - return time.time() + offset_s, item - - return item2kv
- -
[docs] @staticmethod - def item_to_key_params_and_val(item_to_key_params_and_val, key_str_format): - """Make item2kv from a function that produces key_params and val, - and a key_template that will produce a string key from the key_params - - Args: - item_to_key_params_and_val: an item -> (key_params, val) function - key_str_format: A string format such that - key_str_format.format(*key_params) or - key_str_format.format(**key_params) - will produce the desired key string - - Returns: an item -> (key, val) function - - >>> # Using tuple key params with unnamed string format fields - >>> item_to_kv = mk_item2kv_for.item_to_key_params_and_val(lambda x: ((x['L'], x['I']), x['G']), '{}/{}') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('let/it', 'go') - >>> - >>> # Using dict key params with named string format fields - >>> item_to_kv = mk_item2kv_for.item_to_key_params_and_val( - ... lambda x: ({'second': x['L'], 'first': x['G']}, x['I']), '{first}_{second}') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ('go_let', 'it') - """ - - def item2kv(item): - key_params, val = item_to_key_params_and_val(item) - if isinstance(key_params, dict): - return key_str_format.format(**key_params), val - else: - return key_str_format.format(*key_params), val - - return item2kv
- -
[docs] @staticmethod - def fields(fields, keep_field_in_value=False, key_as_tuple=False): - """Make item2kv from specific fields of a Mapping (i.e. dict-like object) item. - - Note: item2kv will not mutate item (even if keep_field_in_value=False). - - Args: - fields: The sequence (list, tuple, etc.) of item fields that should be used to create the key. - keep_field_in_value: Set to True to return the item as is, as the value - key_as_tuple: Set to True if you want keys to be tuples (note that the fields order is important here!) - - Returns: an item -> (item[fields], item[not in fields]) function - - >>> item_to_kv = mk_item2kv_for.fields('L') - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ({'L': 'let'}, {'I': 'it', 'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(['L', 'I']) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - ({'L': 'let', 'I': 'it'}, {'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), keep_field_in_value=True) - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) # note the order of the key is not ('G', 'L')... - ({'L': 'let', 'G': 'go'}, {'L': 'let', 'I': 'it', 'G': 'go'}) - >>> item_to_kv = mk_item2kv_for.fields(('G', 'L'), key_as_tuple=True) # but ('G', 'L') order is respected here - >>> item_to_kv({'L': 'let', 'I': 'it', 'G': 'go'}) - (('go', 'let'), {'I': 'it'}) - - """ - if isinstance(fields, str): - fields_set = {fields} - fields = (fields,) - else: - fields_set = set(fields) - - def item2kv(item): - if keep_field_in_value: - key = dict() - for k, v in item.items(): - if k in fields_set: - key[k] = v - val = item - else: - key = dict() - val = dict() - for k, v in item.items(): - if k in fields_set: - key[k] = v - elif not keep_field_in_value: - val[k] = v - - if key_as_tuple: - return tuple(key[f] for f in fields), val - else: - return key, val - - return item2kv
- - -from collections.abc import Sequence -from typing import Iterable, Optional - -NotAVal = type( - "NotAVal", (), {} -)() # singleton instance to distinguish from None - - -
[docs]class FixedSizeStack(Sequence): - """A finite Sequence that can have no more than one element. - - >>> t = FixedSizeStack(maxsize=1) - >>> assert len(t) == 0 - >>> - >>> t.append('something') - >>> assert len(t) == 1 - >>> assert t[0] == 'something' - >>> - >>> t.append('something else') - >>> assert len(t) == 1 # still only one item - >>> assert t[0] == 'something' # still the same item - - Not that we'd ever these methods of FirstAppendOnly, - but know that FirstAppendOnly is a collection.abc.Sequence, so... - - >>> t[:1] == t[:10] == t[::-1] == t[::-10] == t[0:2:10] == list(reversed(t)) == ['something'] - True - >>> - >>> assert t.count('something') == 1 - >>> assert t.index('something') == 0 - - """ - - def __init__(self, iterable: Optional[Iterable] = None, *, maxsize: int): - self.maxsize = maxsize - self.data = [NotAVal] * maxsize - # self.data = (isinstance(iterable, Iterable) and list(iterable)) or [] - # if iterable is not None: - # pass - self.cursor = 0 - - def append(self, v): - if self.cursor < self.maxsize: - self.data[self.cursor] = v - self.cursor += 1 - - def __len__(self): - return self.cursor - - def __getitem__(self, k): - if isinstance(k, int): - if k < self.cursor: - return self.data[k] - else: - raise IndexError( - f"There are only {len(self)} items: You asked for self[{k}]." - ) - elif isinstance(k, slice): - return self.data[: self.cursor][k] - else: - raise IndexError( - f"A {self.__class__} instance can only have one value, or none at all." - )
- - -
[docs]class FirstAppendOnly(Sequence): - """A finite Sequence that can have no more than one element. - - >>> t = FirstAppendOnly() - >>> assert len(t) == 0 - >>> - >>> t.append('something') - >>> assert len(t) == 1 - >>> assert t[0] == 'something' - >>> - >>> t.append('something else') - >>> assert len(t) == 1 # still only one item - >>> assert t[0] == 'something' # still the same item - >>> - >>> # Not that we'd ever these methods of FirstAppendOnly, but know that FirstAppendOnly is a collection.abc.Sequence, so... - >>> t[:1] == t[:10] == t[::-1] == t[::-10] == t[0:2:10] == list(reversed(t)) == ['something'] - <stdin>:1: RuntimeWarning: coroutine 'AioFileBytesPersister.asetitem' was never awaited - RuntimeWarning: Enable tracemalloc to get the object allocation traceback - True - >>> - >>> t.count('something') == 1 - True - >>> t.index('something') == 0 - True - """ - - def __init__(self): - self.val = NotAVal - - def append(self, v): - if self.val == NotAVal: - self.val = v - - def __len__(self): - return int(self.val != NotAVal) - - def __getitem__(self, k): - if len(self) == 0: - raise IndexError( - f"There are no items in this {self.__class__} instance" - ) - elif k == 0: - return self.val - elif isinstance(k, slice): - return [self.val][k] - else: - raise IndexError( - f"A {self.__class__} instance can only have one value, or none at all." - )
- - # @staticmethod - # def from - - -# def add_append_functionality_to_str_key_store(store_cls, -# item_to_key_params_and_val, -# key_template=None, -# new_store_name=None): -# def item_to_kv(item): -# nonlocal key_template -# if key_template is None: -# key_params, _ = item_to_key_params_and_val(item) -# key_template = path_sep.join('{{{}}}'.format(p) for p in key_params) -# key_params, val = item_to_key_params_and_val(item) -# return key_template.format(**key_params), val -# -# return add_append_functionality_to_store_cls(store_cls, item_to_kv, new_store_name) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/attr_dict.html b/docs/_modules/py2store/utils/attr_dict.html deleted file mode 100644 index cf6302e..0000000 --- a/docs/_modules/py2store/utils/attr_dict.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - py2store.utils.attr_dict — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.attr_dict

-"""
-a data object layer for object attributes
-"""
-from collections import abc
-from keyword import iskeyword
-from warnings import warn
-
-
-
[docs]class AttrMap: - """A read-only façade for navigating a JSON-like object using attribute notation. - Based on Luciano Ramalho's "Fluent Python" book. - - >>> t = AttrMap({'a': {'b': 2, 'foo': 'bar'}, 'b': [1,2,3]}) - >>> t - AttrMap({'a': {'b': 2, 'foo': 'bar'}, 'b': [1, 2, 3]}) - >>> t.a - AttrMap({'b': 2, 'foo': 'bar'}) - >>> t.a.foo - 'bar' - >>> t.b - [1, 2, 3] - """ - - def __new__(cls, arg): # <1> - if isinstance(arg, abc.Mapping): - return super().__new__(cls) # <2> - elif isinstance(arg, abc.MutableSequence): # <3> - return [cls(item) for item in arg] - else: - return arg - - def __init__(self, mapping): - self.__data = {} - identifiers = [] - for key, value in mapping.items(): - if not isinstance(key, str) or not str.isidentifier(key): - identifiers.append(key) - elif iskeyword(key): - key += '_' - self.__data[key] = value - if identifiers: - warn( - f'{len(identifiers)} keys were not identifiers. Namely:\n{identifiers}' - ) - - def __getattr__(self, name): - if hasattr(self.__data, name): - return getattr(self.__data, name) - else: - return AttrMap(self.__data[name]) # <4> - - def __iter__(self): - yield from self.__data - - def __dir__(self): - s = set(dir(type(self))) - s.update(self.__dict__) - s.update(self) - return s - - def __repr__(self): - return f'{self.__class__.__name__}({self.__data})'
- - -def special_dir(self): - s = set(dir(type(self))) - s.update(self.__dict__) - s.update( - filter( - lambda k: isinstance(k, str) - and str.isidentifier(k) - and not iskeyword(k), - self, - ) - ) - return s - - -
[docs]def attr_wrap(cls, name=None): - """Returns a Mapping class that routes attribute access to keys of mapping. - - >>> A = attr_wrap(dict) - >>> t = A({'a_special_attr': 'foo', 'another_attr': 2, # valid identifiers - ... 42: [1, 2], '$invalid': 'identifier', 'class': 'is a reserved keyword'}) # not valid identifiers - >>> # verify that we have the attr we want - >>> assert 'a_special_attr' in dir(t) - >>> assert 'another_attr' in dir(t) - >>> # verify that we DO NOT have the attr we DO NOT want - >>> assert 42 not in dir(t) - >>> assert '$invalid' not in dir(t) - >>> assert 'class' not in dir(t) - """ - return type( - name or f'Attr{cls.__name__}', - (cls,), - {'__getattr__': cls.__getitem__, '__dir__': special_dir}, - )
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/cache_descriptors.html b/docs/_modules/py2store/utils/cache_descriptors.html deleted file mode 100644 index 699d9db..0000000 --- a/docs/_modules/py2store/utils/cache_descriptors.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - - - py2store.utils.cache_descriptors — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.cache_descriptors

-"""
-descriptors to cache data
-"""
-##############################################################################
-# Copyright (c) 2003 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-##############################################################################
-"""Cached properties
-See the CachedProperty class.
-
-This module was taken from https://github.com/zopefoundation/zope.cachedescriptors.
-"""
-
-from functools import update_wrapper
-
-ncaches = 0
-
-
-class _CachedProperty(object):
-    """
-    Cached property implementation class.
-    """
-
-    def __init__(self, func, *names):
-        global ncaches
-        ncaches += 1
-        self.data = (
-            func,
-            names,
-            '_v_cached_property_key_%s' % ncaches,
-            '_v_cached_property_value_%s' % ncaches,
-        )
-        update_wrapper(self, func)
-
-    def __get__(self, inst, class_):
-        if inst is None:
-            return self
-
-        func, names, key_name, value_name = self.data
-
-        key = names and [getattr(inst, name) for name in names]
-        value = getattr(inst, value_name, self)
-
-        if value is not self:
-            # We have a cached value
-            if key == getattr(inst, key_name, self):
-                # Cache is still good!
-                return value
-
-        # We need to compute and cache the value
-
-        value = func(inst)
-        setattr(inst, key_name, key)
-        setattr(inst, value_name, value)
-
-        return value
-
-
-
[docs]def CachedProperty(*args): - """ - CachedProperties. - This is usable directly as a decorator when given names, or when not. Any of these patterns - will work: - * ``@CachedProperty`` - * ``@CachedProperty()`` - * ``@CachedProperty('n','n2')`` - * def thing(self: ...; thing = CachedProperty(thing) - * def thing(self: ...; thing = CachedProperty(thing, 'n') - """ - - if not args: # @CachedProperty() - return ( - _CachedProperty # A callable that produces the decorated function - ) - - arg1 = args[0] - names = args[1:] - if callable( - arg1 - ): # @CachedProperty, *or* thing = CachedProperty(thing, ...) - return _CachedProperty(arg1, *names) - - # @CachedProperty( 'n' ) - # Ok, must be a list of string names. Which means we are used like a factory - # so we return a callable object to produce the actual decorated function - def factory(function): - return _CachedProperty(function, arg1, *names) - - return factory
- - -
[docs]class Lazy(object): - """Lazy Attributes. - """ - - def __init__(self, func, name=None): - if name is None: - name = func.__name__ - self.data = (func, name) - update_wrapper(self, func) - - def __get__(self, inst, class_): - if inst is None: - return self - - func, name = self.data - value = func(inst) - inst.__dict__[name] = value - return value
- - -class readproperty(object): - def __init__(self, func): - self.func = func - update_wrapper(self, func) - - def __get__(self, inst, class_): - if inst is None: - return self - - func = self.func - return func(inst) - - -
[docs]class cachedIn(object): - """Cached property with given cache attribute.""" - - def __init__(self, attribute_name): - self.attribute_name = attribute_name - - def __call__(self, func): - def get(instance): - try: - value = getattr(instance, self.attribute_name) - except AttributeError: - value = func(instance) - setattr(instance, self.attribute_name, value) - return value - - update_wrapper(get, func) - - return property(get)
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/cumul_aggreg_write.html b/docs/_modules/py2store/utils/cumul_aggreg_write.html deleted file mode 100644 index d523832..0000000 --- a/docs/_modules/py2store/utils/cumul_aggreg_write.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - - - - - py2store.utils.cumul_aggreg_write — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.cumul_aggreg_write

-"""
-utils for bulk writing -- accumulate, aggregate and write when some condition is met
-"""
-import itertools
-import time
-from collections import defaultdict
-from functools import reduce
-from operator import add
-
-
-def flush_on_exit(cls):
-    def __enter__(self):
-        return self
-
-    def __exit__(self, *args, **kwargs):
-        return self.flush_cache()
-
-    new_cls = type(cls.__name__, (cls,), {})
-    new_cls.__enter__ = __enter__
-    new_cls.__exit__ = __exit__
-    return new_cls
-
-
-def let_through(gen):
-    yield from gen
-
-
-def key_count(gen, start=0):
-    yield from enumerate(gen, start=start)
-
-
-def join_byte_values_and_key_as_current_utc_milliseconds(gen):
-    k = int(time.time() * 1000)
-    yield k, b''.join(gen)
-
-
-def join_string_values_and_key_as_current_utc_milliseconds(gen):
-    k = int(time.time() * 1000)
-    yield k, ''.join(gen)
-
-
-def mk_kv_from_keygen(keygen=itertools.count()):
-    def aggregate(gen):
-        for k, v in zip(keygen, gen):
-            yield k, v
-
-    return aggregate
-
-
-infinite_keycount_kvs = mk_kv_from_keygen(keygen=itertools.count())
-no_initial = type('NoInitial', (), {})()
-
-
-
[docs]def mk_group_aggregator(item_to_kv, aggregator_op=add, initial=no_initial): - """Make a generator transforming function that will - (a) make a key for each given item, - (b) group all items according to the key - - Args: - item_to_kv: - aggregator_op: - initial: - - Returns: - - >>> # Collect words (as a csv string), grouped by the lower case of the first letter - >>> ag = mk_group_aggregator(lambda item: (item[0].lower(), item), - ... aggregator_op=lambda x, y: ', '.join([x, y])) - >>> list(ag(['apple', 'bananna', 'Airplane'])) - [('a', 'apple, Airplane'), ('b', 'bananna')] - >>> # Collect (and concatinate) characters according to their ascii value modulo 3 - >>> ag = mk_group_aggregator(lambda item: (item['age'], item['thing']), - ... aggregator_op=lambda x, y: x + [y], - ... initial=[]) - >>> list(ag([{'age': 0, 'thing': 'new'}, {'age': 42, 'thing': 'every'}, {'age': 0, 'thing': 'just born'}])) - [(0, ['new', 'just born']), (42, ['every'])] - """ - if initial is no_initial: - aggregate_reduce = lambda v: reduce(aggregator_op, v) - else: - aggregate_reduce = lambda v: reduce(aggregator_op, v, initial) - - def aggregator(gen): - d = defaultdict(list) - for k, v in map(item_to_kv, gen): - d[k].append(v) - yield from ((k, aggregate_reduce(v)) for k, v in d.items()) - - return aggregator
- - -
[docs]def mk_group_aggregator_with_key_func( - item_to_key, aggregator_op=add, initial=no_initial -): - """Make a generator transforming function that will - (a) make a key for each given item, - (b) group all items according to the key - - Args: - item_to_key: Function that takes an item of the generator and outputs the key that should be used to group items - aggregator_op: The aggregation binary function that is used to aggregate two items together. - The function is used as is by the functools.reduce, applied to the sequence of items that were collected for - a given group - initial: The "empty" element to start the reduce (aggregation) with, if necessary. - - Returns: - - >>> # Collect words (as a csv string), grouped by the lower case of the first letter - >>> ag = mk_group_aggregator_with_key_func(lambda item: item[0].lower(), - ... aggregator_op=lambda x, y: ', '.join([x, y])) - >>> list(ag(['apple', 'bananna', 'Airplane'])) - [('a', 'apple, Airplane'), ('b', 'bananna')] - >>> - >>> # Collect (and concatenate) characters according to their ascii value modulo 3 - ... ag = mk_group_aggregator_with_key_func(lambda item: (ord(item) % 3)) - >>> list(ag('abcdefghijklmnop')) - [(1, 'adgjmp'), (2, 'behkn'), (0, 'cfilo')] - >>> - >>> # sum all even and odd number separately - ... ag = mk_group_aggregator_with_key_func(lambda item: (item % 2)) - >>> list(ag([1, 2, 3, 4, 5])) # sum of evens is 6, and sum of odds is 9 - [(1, 9), (0, 6)] - >>> - >>> # if we wanted to collect all odds and evens, we'd need a different aggregator and initial - ... ag = mk_group_aggregator_with_key_func(lambda item: (item % 2), aggregator_op=lambda x, y: x + [y], initial=[]) - >>> list(ag([1, 2, 3, 4, 5])) - [(1, [1, 3, 5]), (0, [2, 4])] - """ - return mk_group_aggregator( - item_to_kv=lambda item: (item_to_key(item), item), - aggregator_op=aggregator_op, - initial=initial, - )
- - -
[docs]@flush_on_exit -class CumulAggregWrite: - """ - >>> store = dict() - >>> key_count = lambda gen: enumerate(gen, start=0) - >>> caw = CumulAggregWrite(store, cache_to_kv=key_count) - >>> # Adding 3 items... - >>> caw.append(3) - >>> caw.append('hi') - >>> caw.append({'a': complex, 'obj': [1,2,3]}) - >>> - >>> caw.cache # The cache now has 3 items - [3, 'hi', {'a': <class 'complex'>, 'obj': [1, 2, 3]}] - >>> caw.store # Store is still empty - {} - >>> # Flushing the items ############# - >>> caw.flush_cache() - >>> caw.cache # The cache now has no more items - [] - >>> caw.store # But the store has them. - {0: 3, 1: 'hi', 2: {'a': <class 'complex'>, 'obj': [1, 2, 3]}} - >>> - >>> # One common use case of aggregating is when data is actually grouped and aggregated for storage - >>> - """ - - def __init__( - self, store, cache_to_kv=infinite_keycount_kvs, mk_cache=list - ): - self.store = store - self.cache_to_kv = cache_to_kv - self._mk_cache = mk_cache - self.cache = ( - mk_cache() - ) # Note: Better a cache factory, or the same object with an empty() method. - - def __setitem__(self, k, v): - self.cache.append((k, v)) - - def append(self, item): - self.cache.append(item) - - def extend(self, items): - for item in items: - self.append(item) - - def flush_cache(self): - for k, v in self.cache_to_kv(self.cache): - self.store[k] = v - self.cache = self._mk_cache() - - def close(self): - return self.flush_cache()
- - -
[docs]class CumulAggregWriteKvItems(CumulAggregWrite): - def __init__(self, store): - super().__init__( - store, cache_to_kv=lambda gen: iter(gen), mk_cache=list - )
- - -
[docs]def condition_flush_on_every_write(cache): - """Boolean function used as flush_cache_condition to anytime the cache is non-empty""" - return len(cache) > 0
- - -
[docs]class CumulAggregWriteWithAutoFlush(CumulAggregWrite): - def __init__( - self, - store, - cache_to_kv=infinite_keycount_kvs, - mk_cache=list, - flush_cache_condition=condition_flush_on_every_write, - ): - super().__init__(store, cache_to_kv, mk_cache) - self.flush_cache_condition = flush_cache_condition - - def __setitem__(self, k, v): - super().__setitem__(k, v) - if self.flush_cache_condition(self.cache): - self.flush_cache() - - def append(self, item): - super().append(item) - if self.flush_cache_condition(self.cache): - self.flush_cache()
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/explicit.html b/docs/_modules/py2store/utils/explicit.html deleted file mode 100644 index c19285b..0000000 --- a/docs/_modules/py2store/utils/explicit.html +++ /dev/null @@ -1,478 +0,0 @@ - - - - - - - - py2store.utils.explicit — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.explicit

-"""
-utils to make stores based on a the input data itself
-"""
-from collections.abc import Mapping
-from typing import Callable, Collection as CollectionType
-
-from py2store import Store
-from py2store.base import Collection, KvReader
-from py2store.trans import kv_wrap
-from py2store.paths import PrefixRelativizationMixin
-from py2store.util import max_common_prefix
-
-
-class ObjLoader(object):
-    def __init__(self, data_of_key, obj_of_data=None):
-        self.data_of_key = data_of_key
-        if obj_of_data is not None or not callable(obj_of_data):
-            raise TypeError('serializer must be None or a callable')
-        self.obj_of_data = obj_of_data
-
-    def __call__(self, k):
-        if self.obj_of_data is not None:
-            return self.obj_of_data(self.data_of_key(k))
-        else:
-            return self.data_of_key(k)
-
-
-
[docs]class ObjReader: - """ - A reader that uses a specified function to get the contents for a given key. - - >>> # define a contents_of_key that reads stuff from a dict - >>> data = {'foo': 'bar', 42: "everything"} - >>> def read_dict(k): - ... return data[k] - >>> pr = ObjReader(_obj_of_key=read_dict) - >>> pr['foo'] - 'bar' - >>> pr[42] - 'everything' - >>> - >>> # define contents_of_key that reads stuff from a file given it's path - >>> def read_file(path): - ... with open(path) as fp: - ... return fp.read() - >>> pr = ObjReader(_obj_of_key=read_file) - >>> file_where_this_code_is = __file__ # it should be THIS file you're reading right now! - >>> print(pr[file_where_this_code_is][62:155]) # print some characters of this file - from collections.abc import Mapping - from typing import Callable, Collection as CollectionType - """ - - def __init__(self, _obj_of_key: Callable): - self._obj_of_key = _obj_of_key - - @classmethod - def from_composition(cls, data_of_key, obj_of_data=None): - return cls( - _obj_of_key=ObjLoader( - data_of_key=data_of_key, obj_of_data=obj_of_data - ) - ) - - def __getitem__(self, k): - try: - return self._obj_of_key(k) - except Exception as e: - raise KeyError( - 'KeyError in {} when trying to __getitem__({}): {}'.format( - e.__class__.__name__, k, e - ) - )
- - -# TODO: Revisit ExplicitKeys and ExplicitKeysWithPrefixRelativization. Not extendible to full store! -
[docs]class ExplicitKeys(Collection): - """ - py2store.base.Keys implementation that gets it's keys explicitly from a collection given at initialization time. - The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.) - - >>> keys = ExplicitKeys(key_collection=['foo', 'bar', 'alice']) - >>> 'foo' in keys - True - >>> 'not there' in keys - False - >>> list(keys) - ['foo', 'bar', 'alice'] - """ - - __slots__ = ('_keys_cache',) - - def __init__( - self, key_collection: CollectionType - ): # don't remove this init: Don't. Need for _keys_cache init - assert isinstance(key_collection, CollectionType), ( - 'key_collection must be a collections.abc.Collection, i.e. have a __len__, __contains__, and __len__.' - 'The key_collection you gave me was a {}'.format( - type(key_collection) - ) - ) - # self._key_collection = key_collection - self._keys_cache = key_collection - - def __iter__(self): - yield from self._keys_cache - - def __len__(self): - return len(self._keys_cache) - - def __contains__(self, k): - return k in self._keys_cache
- - -
[docs]class ExplicitKeysSource(ExplicitKeys, ObjReader, KvReader): - """ - An object source that uses an explicit keys collection and a specified function to read contents for a key. - """ - - def __init__(self, key_collection: CollectionType, _obj_of_key: Callable): - """ - - :param key_collection: The collection of keys that this source handles - :param _obj_of_key: The function that returns the contents for a key - """ - ObjReader.__init__(self, _obj_of_key) - self._keys_cache = key_collection
- - -
[docs]class ExplicitKeysStore(ExplicitKeys, Store): - """Wrap a store (instance) so that it gets it's keys from an explicit iterable of keys. - - >>> s = {'a': 1, 'b': 2, 'c': 3, 'd': 4} - >>> list(s) - ['a', 'b', 'c', 'd'] - >>> ss = ExplicitKeysStore(s, ['d', 'a']) - >>> len(ss) - 2 - >>> list(ss) - ['d', 'a'] - >>> list(ss.values()) - [4, 1] - >>> ss.head() - ('d', 4) - """ - - def __init__(self, store, key_collection): - Store.__init__(self, store) - self._keys_cache = key_collection
- - -
[docs]def invertible_maps(mapping=None, inv_mapping=None): - """Returns two maps that are inverse of each other. - Raises an AssertionError iif both maps are None, or if the maps are not inverse of each other - - Get a pair of invertible maps - >>> invertible_maps({1: 11, 2: 22}) - ({1: 11, 2: 22}, {11: 1, 22: 2}) - >>> invertible_maps(None, {11: 1, 22: 2}) - ({1: 11, 2: 22}, {11: 1, 22: 2}) - - If two maps are given and invertible, you just get them back - >>> invertible_maps({1: 11, 2: 22}, {11: 1, 22: 2}) - ({1: 11, 2: 22}, {11: 1, 22: 2}) - - Or if they're not invertible - >>> invertible_maps({1: 11, 2: 22}, {11: 1, 22: 'ha, not what you expected!'}) - Traceback (most recent call last): - ... - AssertionError: mapping and inv_mapping are not inverse of each other! - - >>> invertible_maps(None, None) - Traceback (most recent call last): - ... - ValueError: You need to specify one or both maps - """ - if inv_mapping is None and mapping is None: - raise ValueError('You need to specify one or both maps') - if inv_mapping is None: - assert hasattr(mapping, 'items') - inv_mapping = {v: k for k, v in mapping.items()} - assert len(inv_mapping) == len( - mapping - ), 'The values of mapping are not unique, so the mapping is not invertible' - elif mapping is None: - assert hasattr(inv_mapping, 'items') - mapping = {v: k for k, v in inv_mapping.items()} - assert len(mapping) == len( - inv_mapping - ), 'The values of inv_mapping are not unique, so the mapping is not invertible' - else: - assert (len(mapping) == len(inv_mapping)) and ( - mapping == {v: k for k, v in inv_mapping.items()} - ), 'mapping and inv_mapping are not inverse of each other!' - - return mapping, inv_mapping
- - -class ExplicitKeyMap: - def __init__( - self, *, key_of_id: Mapping = None, id_of_key: Mapping = None - ): - """ - - :param key_of_id: - :param id_of_key: - - >>> km = ExplicitKeyMap(key_of_id={'a': 1, 'b': 2}) - >>> km.id_of_key = {1: 'a', 2: 'b'} - >>> km._key_of_id('b') - 2 - >>> km._id_of_key(1) - 'a' - >>> # You can specify id_of_key instead - >>> km = ExplicitKeyMap(id_of_key={1: 'a', 2: 'b'}) - >>> assert km.key_of_id_map == {'a': 1, 'b': 2} - >>> # You can specify both key_of_id and id_of_key - >>> km = ExplicitKeyMap(key_of_id={'a': 1, 'b': 2}, id_of_key={1: 'a', 2: 'b'}) - >>> assert km._key_of_id(km._id_of_key(2)) == 2 - >>> assert km._id_of_key(km._key_of_id('b')) == 'b' - >>> # But they better be inverse of each other! - >>> km = ExplicitKeyMap(key_of_id={'a': 1, 'b': 2, 'c': 2}) - Traceback (most recent call last): - ... - AssertionError: The values of inv_mapping are not unique, so the mapping is not invertible - >>> km = ExplicitKeyMap(key_of_id={'a': 1, 'b': 2}, id_of_key={1: 'a', 2: 'oh no!!!!'}) - Traceback (most recent call last): - ... - AssertionError: mapping and inv_mapping are not inverse of each other! - """ - id_of_key, key_of_id = invertible_maps(id_of_key, key_of_id) - self.key_of_id_map = key_of_id - self.id_of_key_map = id_of_key - - def _key_of_id(self, _id): - return self.key_of_id_map[_id] - - def _id_of_key(self, k): - return self.id_of_key_map[k] - - -
[docs]class ExplicitKeymapReader(ExplicitKeys, Store): - """Wrap a store (instance) so that it gets it's keys from an explicit iterable of keys. - - >>> s = {'a': 1, 'b': 2, 'c': 3, 'd': 4} - >>> id_of_key = {'A': 'a', 'C': 'c'} - >>> ss = ExplicitKeymapReader(s, id_of_key=id_of_key) - >>> list(ss) - ['A', 'C'] - >>> ss['C'] # will look up 'C', find 'c', and call the store on that. - 3 - """ - - def __init__(self, store, key_of_id=None, id_of_key=None): - key_trans = ExplicitKeyMap(key_of_id=key_of_id, id_of_key=id_of_key) - Store.__init__(self, kv_wrap(key_trans)(store)) - ExplicitKeys.__init__(self, key_trans.id_of_key_map.keys())
- - -
[docs]class ExplicitKeysWithPrefixRelativization(PrefixRelativizationMixin, Store): - """ - py2store.base.Keys implementation that gets it's keys explicitly from a collection given at initialization time. - The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.) - - >>> from py2store.base import Store - >>> s = ExplicitKeysWithPrefixRelativization(key_collection=['/root/of/foo', '/root/of/bar', '/root/for/alice']) - >>> keys = Store(store=s) - >>> 'of/foo' in keys - True - >>> 'not there' in keys - False - >>> list(keys) - ['of/foo', 'of/bar', 'for/alice'] - """ - - __slots__ = ('_key_collection',) - - def __init__(self, key_collection, _prefix=None): - if _prefix is None: - _prefix = max_common_prefix(key_collection) - store = ExplicitKeys(key_collection=key_collection) - self._prefix = _prefix - super().__init__(store=store)
- - -class ObjDumper(object): - def __init__(self, save_data_to_key, data_of_obj=None): - self.save_data_to_key = save_data_to_key - if data_of_obj is not None or not callable(data_of_obj): - raise TypeError('serializer must be None or a callable') - self.data_of_obj = data_of_obj - - def __call__(self, k, v): - if self.data_of_obj is not None: - return self.save_data_to_key(k, self.data_of_obj(v)) - else: - return self.save_data_to_key(k, v) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/glom.html b/docs/_modules/py2store/utils/glom.html deleted file mode 100644 index b8168fe..0000000 --- a/docs/_modules/py2store/utils/glom.html +++ /dev/null @@ -1,2434 +0,0 @@ - - - - - - - - py2store.utils.glom — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.glom

-"""*glom is a util to extract stuff from nested structures.*
-It's one of those excellent utils that I've written many times, but never got quite right.
-Mahmoud Hashemi got it right.
-
-:BEGIN LICENSE:
-
-Copyright (c) 2018, Mahmoud Hashemi
-
-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.
-
-    * The names of the contributors may not be used to endorse or
-      promote products derived from this software without specific
-      prior written permission.
-
-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
-OWNER 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.
-
-:END LICENSE:
-
-
-Now, at the time of writing this, I've already transformed it to bend it to my liking.
-At some point it may become something else, but I wanted there to be a trace of what my seed was.
-Though I can't promise I'll maintain the same functionality as I transform this module, here's
-a tutorial on how to use it in it's original form:
-    https://glom.readthedocs.io/en/latest/
-
-
-I only took the main (core) module from the glom project.
-Here's the original docs of this glom module.
-
-If there was ever a Python example of "big things come in small
-packages", ``glom`` might be it.
-
-The ``glom`` package has one central entrypoint,
-:func:`glom.glom`. Everything else in the package revolves around that
-one function.
-
-A couple of conventional terms you'll see repeated many times below:
-
-* **target** - glom is built to work on any data, so we simply
-  refer to the object being accessed as the *"target"*
-* **spec** - *(aka "glomspec", short for specification)* The
-  accompanying template used to specify the structure of the return
-  value.
-
-Now that you know the terms, let's take a look around glom's powerful
-semantics.
-
-"""
-
-import pdb
-import weakref
-import operator
-from abc import ABCMeta
-from pprint import pprint
-from collections import OrderedDict, ChainMap
-
-from py2store.util import format_invocation
-
-_AbstractIterableBase = ABCMeta('_AbstractIterableBase', (object,), {})
-
-_type_type = type
-
-
-
[docs]def make_sentinel(name='_MISSING', var_name=None): - """Creates and returns a new **instance** of a new class, suitable for - usage as a "sentinel", a kind of singleton often used to indicate - a value is missing when ``None`` is a valid input. - - Args: - name (str): Name of the Sentinel - var_name (str): Set this name to the name of the variable in - its respective module enable pickleability. - - >>> make_sentinel(var_name='_MISSING') - _MISSING - - The most common use cases here in boltons are as default values - for optional function arguments, partly because of its - less-confusing appearance in automatically generated - documentation. Sentinels also function well as placeholders in queues - and linked lists. - - .. note:: - - By design, additional calls to ``make_sentinel`` with the same - values will not produce equivalent objects. - - >>> make_sentinel('TEST') == make_sentinel('TEST') - False - >>> type(make_sentinel('TEST')) == type(make_sentinel('TEST')) - False - - """ - - class Sentinel(object): - def __init__(self): - self.name = name - self.var_name = var_name - - def __repr__(self): - if self.var_name: - return self.var_name - return '%s(%r)' % (self.__class__.__name__, self.name) - - if var_name: - - def __reduce__(self): - return self.var_name - - def __nonzero__(self): - return False - - __bool__ = __nonzero__ - - return Sentinel()
- - -from collections.abc import Iterable - - -
[docs]def is_iterable(x): - """Similar in nature to :func:`callable`, ``is_iterable`` returns - ``True`` if an object is `iterable`_, ``False`` if not. - >>> is_iterable([]) - True - >>> is_iterable(1) - False""" - return isinstance(x, Iterable)
- - -_MISSING = make_sentinel('_MISSING') -SKIP = make_sentinel('SKIP') -SKIP.__doc__ = ''' -The ``SKIP`` singleton can be returned from a function or included -via a :class:`~glom.Literal` to cancel assignment into the output -object. - ->>> target = {'a': 'b'} ->>> spec = {'a': lambda t: t['a'] if t['a'] == 'a' else SKIP} ->>> glom(target, spec) -{} ->>> target = {'a': 'a'} ->>> glom(target, spec) -{'a': 'a'} - -Mostly used to drop keys from dicts (as above) or filter objects from -lists. - -.. note:: - - SKIP was known as OMIT in versions 18.3.1 and prior. Versions 19+ - will remove the OMIT alias entirely. -''' -OMIT = SKIP # backwards compat, remove in 19+ - -STOP = make_sentinel('STOP') -STOP.__doc__ = ''' -The ``STOP`` singleton can be used to halt iteration of a list or -execution of a tuple of subspecs. - ->>> target = range(10) ->>> spec = [lambda x: x if x < 5 else STOP] ->>> glom(target, spec) -[0, 1, 2, 3, 4] -''' - -LAST_CHILD_SCOPE = make_sentinel('LAST_CHILD_SCOPE') -LAST_CHILD_SCOPE.__doc__ = ''' -Marker that can be used by parents to keep track of the last child -scope executed. Useful for "lifting" results out of child scopes -for scopes that want to chain the scopes of their children together -similar to tuple. -''' -MODE = make_sentinel('MODE') - - -
[docs]class GlomError(Exception): - """The base exception for all the errors that might be raised from - :func:`glom` processing logic. - - By default, exceptions raised from within functions passed to glom - (e.g., ``len``, ``sum``, any ``lambda``) will not be wrapped in a - GlomError. - """ - - pass
- - -
[docs]class PathAccessError(AttributeError, KeyError, IndexError, GlomError): - """This :exc:`GlomError` subtype represents a failure to access an - attribute as dictated by the spec. The most commonly-seen error - when using glom, it maintains a copy of the original exception and - produces a readable error message for easy debugging. - - If you see this error, you may want to: - - * Check the target data is accurate using :class:`~glom.Inspect` - * Catch the exception and return a semantically meaningful error message - * Use :class:`glom.Coalesce` to specify a default - * Use the top-level ``default`` kwarg on :func:`~glom.glom()` - - In any case, be glad you got this error and not the one it was - wrapping! - - Args: - exc (Exception): The error that arose when we tried to access - *path*. Typically an instance of KeyError, AttributeError, - IndexError, or TypeError, and sometimes others. - path (Path): The full Path glom was in the middle of accessing - when the error occurred. - part_idx (int): The index of the part of the *path* that caused - the error. - - >>> target = {'a': {'b': None}} - >>> glom(target, 'a.b.c') # doctest: +SKIP - Traceback (most recent call last): - ... - glom.PathAccessError: could not access 'c', part 2 of Path('a', 'b', 'c'), got error: ... - - """ - - def __init__(self, exc, path, part_idx): - self.exc = exc - self.path = path - self.part_idx = part_idx - - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%r, %r, %r)' % (cn, self.exc, self.path, self.part_idx) - - def __str__(self): - return 'could not access %r, part %r of %r, got error: %r' % ( - self.path.values()[self.part_idx], - self.part_idx, - self.path, - self.exc, - )
- - -
[docs]class CoalesceError(GlomError): - """This :exc:`GlomError` subtype is raised from within a - :class:`Coalesce` spec's processing, when none of the subspecs - match and no default is provided. - - The exception object itself keeps track of several values which - may be useful for processing: - - Args: - coal_obj (Coalesce): The original failing spec, see - :class:`Coalesce`'s docs for details. - skipped (list): A list of ignored values and exceptions, in the - order that their respective subspecs appear in the original - *coal_obj*. - path: Like many GlomErrors, this exception knows the path at - which it occurred. - - >>> target = {} - >>> glom(target, Coalesce('a', 'b')) # doctest: +SKIP - Traceback (most recent call last): - ... - glom.CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) ... - """ - - def __init__(self, coal_obj, skipped, path): - self.coal_obj = coal_obj - self.skipped = skipped - self.path = path - - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%r, %r, %r)' % (cn, self.coal_obj, self.skipped, self.path) - - def __str__(self): - missed_specs = tuple(self.coal_obj.subspecs) - skipped_vals = [ - v.__class__.__name__ - if isinstance(v, self.coal_obj.skip_exc) - else '<skipped %s>' % v.__class__.__name__ - for v in self.skipped - ] - msg = 'no valid values found. Tried %r and got (%s)' % ( - missed_specs, - ', '.join(skipped_vals), - ) - if self.coal_obj.skip is not _MISSING: - msg += ', skip set to %r' % (self.coal_obj.skip,) - if self.coal_obj.skip_exc is not GlomError: - msg += ', skip_exc set to %r' % (self.coal_obj.skip_exc,) - if self.path is not None: - msg += ' (at path %r)' % (self.path,) - return msg
- - -
[docs]class UnregisteredTarget(GlomError): - """This :class:`GlomError` subtype is raised when a spec calls for an - unsupported action on a target type. For instance, trying to - iterate on an non-iterable target: - - >>> glom(object(), ['a.b.c']) # doctest: +SKIP - Traceback (most recent call last): - ... - glom.UnregisteredTarget: target type 'object' not registered for 'iterate', expected one of registered types: (...) - - It should be noted that this is a pretty uncommon occurrence in - production glom usage. See the :ref:`setup-and-registration` - section for details on how to avoid this error. - - An UnregisteredTarget takes and tracks a few values: - - Args: - op (str): The name of the operation being performed ('get' or 'iterate') - target_type (type): The type of the target being processed. - type_map (dict): A mapping of target types that do support this operation - path: The path at which the error occurred. - - """ - - def __init__(self, op, target_type, type_map, path): - self.op = op - self.target_type = target_type - self.type_map = type_map - self.path = path - - def __repr__(self): - cn = self.__class__.__name__ - # <type %r> is because Python 3 inexplicably changed the type - # repr from <type *> to <class *> - return '%s(%r, <type %r>, %r, %r)' % ( - cn, - self.op, - self.target_type.__name__, - self.type_map, - self.path, - ) - - def __str__(self): - if not self.type_map: - return ( - "glom() called without registering any types for operation '%s'. see" - " glom.register() or Glommer's constructor for details." - % (self.op,) - ) - reg_types = sorted([t.__name__ for t, h in self.type_map.items() if h]) - reg_types_str = ( - '()' if not reg_types else ('(%s)' % ', '.join(reg_types)) - ) - msg = ( - "target type %r not registered for '%s', expected one of" - ' registered types: %s' - % (self.target_type.__name__, self.op, reg_types_str) - ) - if self.path: - msg += ' (at %r)' % (self.path,) - return msg
- - -
[docs]class Path(object): - """Path objects specify explicit paths when the default - ``'a.b.c'``-style general access syntax won't work or isn't - desirable. Use this to wrap ints, datetimes, and other valid - keys, as well as strings with dots that shouldn't be expanded. - - >>> target = {'a': {'b': 'c', 'd.e': 'f', 2: 3}} - >>> glom(target, Path('a', 2)) - 3 - >>> glom(target, Path('a', 'd.e')) - 'f' - - Paths can be used to join together other Path objects, as - well as :data:`~glom.T` objects: - - >>> Path(T['a'], T['b']) - T['a']['b'] - >>> Path(Path('a', 'b'), Path('c', 'd')) - Path('a', 'b', 'c', 'd') - - Paths also support indexing and slicing, with each access - returning a new Path object: - - >>> path = Path('a', 'b', 1, 2) - >>> path[0] - Path('a') - >>> path[-2:] - Path(1, 2) - """ - - def __init__(self, *path_parts): - if not path_parts: - self.path_t = T - return - if isinstance(path_parts[0], TType): - path_t = path_parts[0] - offset = 1 - else: - path_t = T - offset = 0 - for part in path_parts[offset:]: - if isinstance(part, Path): - part = part.path_t - if isinstance(part, TType): - sub_parts = _T_PATHS[part] - if sub_parts[0] is not T: - raise ValueError( - 'path segment must be path from T, not %r' - % sub_parts[0] - ) - i = 1 - while i < len(sub_parts): - path_t = _t_child(path_t, sub_parts[i], sub_parts[i + 1]) - i += 2 - else: - path_t = _t_child(path_t, 'P', part) - self.path_t = path_t - -
[docs] @classmethod - def from_text(cls, text): - """Make a Path from .-delimited text: - - >>> Path.from_text('a.b.c') - Path('a', 'b', 'c') - - """ - return cls(*text.split('.'))
- - def glomit(self, target, scope): - # The entrypoint for the Path extension - return _t_eval(target, self.path_t, scope) - - def __len__(self): - return (len(_T_PATHS[self.path_t]) - 1) // 2 - - def __eq__(self, other): - if type(other) is Path: - return _T_PATHS[self.path_t] == _T_PATHS[other.path_t] - elif type(other) is TType: - return _T_PATHS[self.path_t] == _T_PATHS[other] - return False - - def __ne__(self, other): - return not self == other - -
[docs] def values(self): - """ - Returns a tuple of values referenced in this path. - - >>> Path(T.a.b, 'c', T['d']).values() - ('a', 'b', 'c', 'd') - """ - cur_t_path = _T_PATHS[self.path_t] - return cur_t_path[2::2]
- -
[docs] def items(self): - """ - Returns a tuple of (operation, value) pairs. - - >>> Path(T.a.b, 'c', T['d']).items() - (('.', 'a'), ('.', 'b'), ('P', 'c'), ('[', 'd')) - - """ - cur_t_path = _T_PATHS[self.path_t] - return tuple(zip(cur_t_path[1::2], cur_t_path[2::2]))
- - def startswith(self, other): - if isinstance(other, str): - other = Path(other) - if isinstance(other, Path): - other = other.path_t - if not isinstance(other, TType): - raise TypeError( - 'can only check if Path starts with string, Path or T' - ) - o_path = _T_PATHS[other] - return _T_PATHS[self.path_t][: len(o_path)] == o_path - -
[docs] def from_t(self): - """return the same path but starting from T""" - t_path = _T_PATHS[self.path_t] - if t_path[0] is S: - new_t = TType() - _T_PATHS[new_t] = (T,) + t_path[1:] - return Path(new_t) - return self
- - def __getitem__(self, i): - cur_t_path = _T_PATHS[self.path_t] - try: - step = i.step - start = i.start if i.start is not None else 0 - stop = i.stop - - start = ( - (start * 2) + 1 - if start >= 0 - else (start * 2) + len(cur_t_path) - ) - if stop is not None: - stop = ( - (stop * 2) + 1 - if stop >= 0 - else (stop * 2) + len(cur_t_path) - ) - except AttributeError: - step = 1 - start = (i * 2) + 1 if i >= 0 else (i * 2) + len(cur_t_path) - if start < 0 or start > len(cur_t_path): - raise IndexError('Path index out of range') - stop = ( - ((i + 1) * 2) + 1 - if i >= 0 - else ((i + 1) * 2) + len(cur_t_path) - ) - - new_t = TType() - new_path = cur_t_path[start:stop] - if step is not None and step != 1: - new_path = tuple(zip(new_path[::2], new_path[1::2]))[::step] - new_path = sum(new_path, ()) - _T_PATHS[new_t] = (cur_t_path[0],) + new_path - return Path(new_t) - - def __repr__(self): - return _format_path(_T_PATHS[self.path_t][1:])
- - -def _format_path(t_path): - path_parts, cur_t_path = [], [] - i = 0 - while i < len(t_path): - op, arg = t_path[i], t_path[i + 1] - i += 2 - if op == 'P': - if cur_t_path: - path_parts.append(cur_t_path) - cur_t_path = [] - path_parts.append(arg) - else: - cur_t_path.append(op) - cur_t_path.append(arg) - if path_parts and cur_t_path: - path_parts.append(cur_t_path) - - if path_parts or not cur_t_path: - return 'Path(%s)' % ', '.join( - [ - _format_t(part) if type(part) is list else repr(part) - for part in path_parts - ] - ) - return _format_t(cur_t_path) - - -
[docs]class Literal(object): - """Literal objects specify literal values in rare cases when part of - the spec should not be interpreted as a glommable - subspec. Wherever a Literal object is encountered in a spec, it is - replaced with its wrapped *value* in the output. - - >>> target = {'a': {'b': 'c'}} - >>> spec = {'a': 'a.b', 'readability': Literal('counts')} - >>> pprint(glom(target, spec)) - {'a': 'c', 'readability': 'counts'} - - Instead of accessing ``'counts'`` as a key like it did with - ``'a.b'``, :func:`~glom.glom` just unwrapped the literal and - included the value. - - :class:`~glom.Literal` takes one argument, the literal value that should appear - in the glom output. - - This could also be achieved with a callable, e.g., ``lambda x: - 'literal_string'`` in the spec, but using a :class:`~glom.Literal` - object adds explicitness, code clarity, and a clean :func:`repr`. - - """ - - def __init__(self, value): - self.value = value - - def glomit(self, target, scope): - return self.value - - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%r)' % (cn, self.value)
- - -
[docs]class Spec(object): - """Spec objects serve three purposes, here they are, roughly ordered - by utility: - - 1. As a form of compiled or "curried" glom call, similar to - Python's built-in :func:`re.compile`. - 2. A marker as an object as representing a spec rather than a - literal value in certain cases where that might be ambiguous. - 3. A way to update the scope within another Spec. - - In the second usage, Spec objects are the complement to - :class:`~glom.Literal`, wrapping a value and marking that it - should be interpreted as a glom spec, rather than a literal value. - This is useful in places where it would be interpreted as a value - by default. (Such as T[key], Call(func) where key and func are - assumed to be literal values and not specs.) - - Args: - spec: The glom spec. - scope (dict): additional values to add to the scope when - evaluating this Spec - - """ - - def __init__(self, spec, scope=None): - self.spec = spec - self.scope = scope or {} - - def glom(self, target, **kw): - scope = dict(self.scope) - scope.update(kw.get('scope', {})) - kw['scope'] = ChainMap(scope) - glom_ = scope.get(glom, glom) - return glom_(target, self.spec, **kw) - - def glomit(self, target, scope): - scope.update(self.scope) - return scope[glom](target, self.spec, scope) - - def __repr__(self): - cn = self.__class__.__name__ - if self.scope: - return '%s(%r, scope=%r)' % (cn, self.spec, self.scope) - return '%s(%r)' % (cn, self.spec)
- - -
[docs]class Coalesce(object): - """Coalesce objects specify fallback behavior for a list of - subspecs. - - Subspecs are passed as positional arguments, and keyword arguments - control defaults. Each subspec is evaluated in turn, and if none - match, a :exc:`CoalesceError` is raised, or a default is returned, - depending on the options used. - - .. note:: - - This operation may seem very familar if you have experience with - `SQL`_ or even `C# and others`_. - - - In practice, this fallback behavior's simplicity is only surpassed - by its utility: - - >>> target = {'c': 'd'} - >>> glom(target, Coalesce('a', 'b', 'c')) - 'd' - - glom tries to get ``'a'`` from ``target``, but gets a - KeyError. Rather than raise a :exc:`~glom.PathAccessError` as usual, - glom *coalesces* into the next subspec, ``'b'``. The process - repeats until it gets to ``'c'``, which returns our value, - ``'d'``. If our value weren't present, we'd see: - - >>> target = {} - >>> glom(target, Coalesce('a', 'b')) # doctest: +SKIP - Traceback (most recent call last): - ... - glom.CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) (at path []) - - Same process, but because ``target`` is empty, we get a - :exc:`CoalesceError`. If we want to avoid an exception, and we - know which value we want by default, we can set *default*: - - >>> target = {} - >>> glom(target, Coalesce('a', 'b', 'c'), default='d-fault') - 'd-fault' - - ``'a'``, ``'b'``, and ``'c'`` weren't present so we got ``'d-fault'``. - - Args: - - subspecs: One or more glommable subspecs - default: A value to return if no subspec results in a valid value - default_factory: A callable whose result will be returned as a default - skip: A value, tuple of values, or predicate function - representing values to ignore - skip_exc: An exception or tuple of exception types to catch and - move on to the next subspec. Defaults to :exc:`GlomError`, the - parent type of all glom runtime exceptions. - - If all subspecs produce skipped values or exceptions, a - :exc:`CoalesceError` will be raised. For more examples, check out - the :doc:`tutorial`, which makes extensive use of Coalesce. - - .. _SQL: https://en.wikipedia.org/w/index.php?title=Null_(SQL)&oldid=833093792#COALESCE - .. _C# and others: https://en.wikipedia.org/w/index.php?title=Null_coalescing_operator&oldid=839493322#C# - - """ - - def __init__(self, *subspecs, **kwargs): - self.subspecs = subspecs - self._orig_kwargs = dict(kwargs) - self.default = kwargs.pop('default', _MISSING) - self.default_factory = kwargs.pop('default_factory', _MISSING) - if self.default and self.default_factory: - raise ValueError( - 'expected one of "default" or "default_factory", not both' - ) - self.skip = kwargs.pop('skip', _MISSING) - if self.skip is _MISSING: - self.skip_func = lambda v: False - elif callable(self.skip): - self.skip_func = self.skip - elif isinstance(self.skip, tuple): - self.skip_func = lambda v: v in self.skip - else: - self.skip_func = lambda v: v == self.skip - self.skip_exc = kwargs.pop('skip_exc', GlomError) - if kwargs: - raise TypeError( - 'unexpected keyword args: %r' % (sorted(kwargs.keys()),) - ) - - def glomit(self, target, scope): - skipped = [] - for subspec in self.subspecs: - try: - ret = scope[glom](target, subspec, scope) - if not self.skip_func(ret): - break - skipped.append(ret) - except self.skip_exc as e: - skipped.append(e) - continue - else: - if self.default is not _MISSING: - ret = self.default - elif self.default_factory is not _MISSING: - ret = self.default_factory() - else: - raise CoalesceError(self, skipped, scope[Path]) - return ret - - def __repr__(self): - cn = self.__class__.__name__ - return format_invocation(cn, self.subspecs, self._orig_kwargs)
- - -
[docs]class Inspect(object): - """The :class:`~glom.Inspect` specifier type provides a way to get - visibility into glom's evaluation of a specification, enabling - debugging of those tricky problems that may arise with unexpected - data. - - :class:`~glom.Inspect` can be inserted into an existing spec in one of two - ways. First, as a wrapper around the spec in question, or second, - as an argument-less placeholder wherever a spec could be. - - :class:`~glom.Inspect` supports several modes, controlled by - keyword arguments. Its default, no-argument mode, simply echos the - state of the glom at the point where it appears: - - >>> target = {'a': {'b': {}}} - >>> val = glom(target, Inspect('a.b')) # wrapping a spec - --- - path: ['a.b'] - target: {'a': {'b': {}}} - output: {} - --- - - Debugging behavior aside, :class:`~glom.Inspect` has no effect on - values in the target, spec, or result. - - Args: - echo (bool): Whether to print the path, target, and output of - each inspected glom. Defaults to True. - recursive (bool): Whether or not the Inspect should be applied - at every level, at or below the spec that it wraps. Defaults - to False. - breakpoint (bool): This flag controls whether a debugging prompt - should appear before evaluating each inspected spec. Can also - take a callable. Defaults to False. - post_mortem (bool): This flag controls whether exceptions - should be caught and interactively debugged with :mod:`pdb` on - inspected specs. - - All arguments above are keyword-only to avoid overlap with a - wrapped spec. - - .. note:: - - Just like ``pdb.set_trace()``, be careful about leaving stray - ``Inspect()`` instances in production glom specs. - - """ - - def __init__(self, *a, **kw): - self.wrapped = a[0] if a else Path() - self.recursive = kw.pop('recursive', False) - self.echo = kw.pop('echo', True) - breakpoint = kw.pop('breakpoint', False) - if breakpoint is True: - breakpoint = pdb.set_trace - if breakpoint and not callable(breakpoint): - raise TypeError( - 'breakpoint expected bool or callable, not: %r' % breakpoint - ) - self.breakpoint = breakpoint - post_mortem = kw.pop('post_mortem', False) - if post_mortem is True: - post_mortem = pdb.post_mortem - if post_mortem and not callable(post_mortem): - raise TypeError( - 'post_mortem expected bool or callable, not: %r' % post_mortem - ) - self.post_mortem = post_mortem - - def __repr__(self): - return '<INSPECT>' - - def glomit(self, target, scope): - # stash the real handler under Inspect, - # and replace the child handler with a trace callback - scope[Inspect] = scope[glom] - scope[glom] = self._trace - return scope[glom](target, self.wrapped, scope) - - def _trace(self, target, spec, scope): - if not self.recursive: - scope[glom] = scope[Inspect] - if self.echo: - print('---') - print('path: ', scope[Path] + [spec]) - print('target:', target) - if self.breakpoint: - self.breakpoint() - try: - ret = scope[Inspect](target, spec, scope) - except Exception: - if self.post_mortem: - self.post_mortem() - raise - if self.echo: - print('output:', ret) - print('---') - return ret
- - -
[docs]class Call(object): - """:class:`Call` specifies when a target should be passed to a function, - *func*. - - :class:`Call` is similar to :func:`~functools.partial` in that - it is no more powerful than ``lambda`` or other functions, but - it is designed to be more readable, with a better ``repr``. - - Args: - func (callable): a function or other callable to be called with - the target - - :class:`Call` combines well with :attr:`~glom.T` to construct objects. For - instance, to generate a dict and then pass it to a constructor: - - >>> class ExampleClass(object): - ... def __init__(self, attr): - ... self.attr = attr - ... - >>> target = {'attr': 3.14} - >>> glom(target, Call(ExampleClass, kwargs=T)).attr - 3.14 - - This does the same as ``glom(target, lambda target: - ExampleClass(**target))``, but it's easy to see which one reads - better. - - .. note:: - - ``Call`` is mostly for functions. Use a :attr:`~glom.T` object - if you need to call a method. - - .. warning:: - - :class:`Call` has a successor with a fuller-featured API, new - in 19.3.0: the :class:`Invoke` specifier type. - """ - - def __init__(self, func=None, args=None, kwargs=None): - if func is None: - func = T - if not (callable(func) or isinstance(func, (Spec, TType))): - raise TypeError( - 'expected func to be a callable or T' - ' expression, not: %r' % (func,) - ) - if args is None: - args = () - if kwargs is None: - kwargs = {} - self.func, self.args, self.kwargs = func, args, kwargs - -
[docs] def glomit(self, target, scope): - 'run against the current target' - - def _eval(t): - if type(t) in (Spec, TType): - return scope[glom](target, t, scope) - return t - - if type(self.args) is TType: - args = _eval(self.args) - else: - args = [_eval(a) for a in self.args] - if type(self.kwargs) is TType: - kwargs = _eval(self.kwargs) - else: - kwargs = {name: _eval(val) for name, val in self.kwargs.items()} - return _eval(self.func)(*args, **kwargs)
- - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%r, args=%r, kwargs=%r)' % ( - cn, - self.func, - self.args, - self.kwargs, - )
- - -def _is_spec(obj, strict=False): - # a little util for codifying the spec type checking in glom - if isinstance(obj, TType): - return True - if strict: - return type(obj) is Spec - # TODO: revisit line below - return callable(getattr(obj, 'glomit', None)) and not isinstance( - obj, type - ) # pragma: no cover - - -
[docs]class Invoke(object): - """Specifier type designed for easy invocation of callables from glom. - - Args: - func (callable): A function or other callable object. - - ``Invoke`` is similar to :func:`functools.partial`, but with the - ability to set up a "templated" call which interleaves constants and - glom specs. - - For example, the following creates a spec which can be used to - check if targets are integers: - - >>> is_int = Invoke(isinstance).specs(T).constants(int) - >>> glom(5, is_int) - True - - And this composes like any other glom spec: - - >>> target = [7, object(), 9] - >>> glom(target, [is_int]) - [True, False, True] - - Another example, mixing positional and keyword arguments: - - >>> spec = Invoke(sorted).specs(T).constants(key=int, reverse=True) - >>> target = ['10', '5', '20', '1'] - >>> glom(target, spec) - ['20', '10', '5', '1'] - - Invoke also helps with evaluating zero-argument functions: - - >>> glom(target={}, spec=Invoke(int)) - 0 - - (A trivial example, but from timestamps to UUIDs, zero-arg calls do come up!) - - .. note:: - - ``Invoke`` is mostly for functions, object construction, and callable - objects. For calling methods, consider the :attr:`~glom.T` object. - - """ - - def __init__(self, func): - if not callable(func) and not _is_spec(func, strict=True): - raise TypeError( - 'expected func to be a callable or Spec instance,' - ' not: %r' % (func,) - ) - self.func = func - self._args = () - # a registry of every known kwarg to its freshest value as set - # by the methods below. the **kw dict is used as a unique marker. - self._cur_kwargs = {} - -
[docs] @classmethod - def specfunc(cls, spec): - """Creates an :class:`Invoke` instance where the function is - indicated by a spec. - - >>> spec = Invoke.specfunc('func').constants(5) - >>> glom({'func': range}, (spec, list)) - [0, 1, 2, 3, 4] - - """ - return cls(Spec(spec))
- -
[docs] def constants(self, *a, **kw): - """Returns a new :class:`Invoke` spec, with the provided positional - and keyword argument values stored for passing to the - underlying function. - - >>> spec = Invoke(T).constants(5) - >>> glom(range, (spec, list)) - [0, 1, 2, 3, 4] - - Subsequent positional arguments are appended: - - >>> spec = Invoke(T).constants(2).constants(10, 2) - >>> glom(range, (spec, list)) - [2, 4, 6, 8] - - Keyword arguments also work as one might expect: - - >>> round_2 = Invoke(round).constants(ndigits=2).specs(T) - >>> glom(3.14159, round_2) - 3.14 - - :meth:`~Invoke.constants()` and other :class:`Invoke` - methods may be called multiple times, just remember that every - call returns a new spec. - """ - ret = self.__class__(self.func) - ret._args = self._args + ('C', a, kw) - ret._cur_kwargs = dict(self._cur_kwargs) - ret._cur_kwargs.update({k: kw for k, _ in kw.items()}) - return ret
- -
[docs] def specs(self, *a, **kw): - """Returns a new :class:`Invoke` spec, with the provided positional - and keyword arguments stored to be interpreted as specs, with - the results passed to the underlying function. - - >>> spec = Invoke(range).specs('value') - >>> glom({'value': 5}, (spec, list)) - [0, 1, 2, 3, 4] - - Subsequent positional arguments are appended: - - >>> spec = Invoke(range).specs('start').specs('end', 'step') - >>> target = {'start': 2, 'end': 10, 'step': 2} - >>> glom(target, (spec, list)) - [2, 4, 6, 8] - - Keyword arguments also work as one might expect: - - >>> multiply = lambda x, y: x * y - >>> times_3 = Invoke(multiply).constants(y=3).specs(x='value') - >>> glom({'value': 5}, times_3) - 15 - - :meth:`~Invoke.specs()` and other :class:`Invoke` - methods may be called multiple times, just remember that every - call returns a new spec. - """ - ret = self.__class__(self.func) - ret._args = self._args + ('S', a, kw) - ret._cur_kwargs = dict(self._cur_kwargs) - ret._cur_kwargs.update({k: kw for k, _ in kw.items()}) - return ret
- -
[docs] def star(self, args=None, kwargs=None): - """Returns a new :class:`Invoke` spec, with *args* and/or *kwargs* - specs set to be "starred" or "star-starred" (respectively) - - >>> import os.path - >>> spec = Invoke(os.path.join).star(args='path') - >>> target = {'path': ['path', 'to', 'dir']} - >>> glom(target, spec) - 'path/to/dir' - - Args: - args (spec): A spec to be evaluated and "starred" into the - underlying function. - kwargs (spec): A spec to be evaluated and "star-starred" into - the underlying function. - - One or both of the above arguments should be set. - - The :meth:`~Invoke.star()`, like other :class:`Invoke` - methods, may be called multiple times. The *args* and *kwargs* - will be stacked in the order in which they are provided. - """ - if args is None and kwargs is None: - raise TypeError('expected one or both of args/kwargs to be passed') - ret = self.__class__(self.func) - ret._args = self._args + ('*', args, kwargs) - ret._cur_kwargs = dict(self._cur_kwargs) - return ret
- - def __repr__(self): - chunks = [self.__class__.__name__] - fname_map = {'C': 'constants', 'S': 'specs', '*': 'star'} - if type(self.func) is Spec: - chunks.append('.specfunc({!r})'.format(self.func.spec)) - else: - chunks.append('({!r})'.format(self.func)) - for i in range(len(self._args) // 3): - op, args, kwargs = self._args[i * 3 : i * 3 + 3] - fname = fname_map[op] - chunks.append('.{}('.format(fname)) - if op in ('C', 'S'): - chunks.append( - ', '.join( - [repr(a) for a in args] - + [ - '{}={!r}'.format(k, v) - for k, v in kwargs.items() - if self._cur_kwargs[k] is kwargs - ] - ) - ) - else: - if args: - chunks.append('args=' + repr(args)) - if args and kwargs: - chunks.append(', ') - if kwargs: - chunks.append('kwargs=' + repr(kwargs)) - chunks.append(')') - return ''.join(chunks) - - def glomit(self, target, scope): - all_args = [] - all_kwargs = {} - - recurse = lambda spec: scope[glom](target, spec, scope) - func = ( - recurse(self.func) - if _is_spec(self.func, strict=True) - else self.func - ) - - for i in range(len(self._args) // 3): - op, args, kwargs = self._args[i * 3 : i * 3 + 3] - if op == 'C': - all_args.extend(args) - all_kwargs.update( - { - k: v - for k, v in kwargs.items() - if self._cur_kwargs[k] is kwargs - } - ) - elif op == 'S': - all_args.extend([recurse(arg) for arg in args]) - all_kwargs.update( - { - k: recurse(v) - for k, v in kwargs.items() - if self._cur_kwargs[k] is kwargs - } - ) - elif op == '*': - if args is not None: - all_args.extend(recurse(args)) - if kwargs is not None: - all_kwargs.update(recurse(kwargs)) - - return func(*all_args, **all_kwargs)
- - -
[docs]class TType(object): - """``T``, short for "target". A singleton object that enables - object-oriented expression of a glom specification. - - .. note:: - - ``T`` is a singleton, and does not need to be constructed. - - Basically, think of ``T`` as your data's stunt double. Everything - that you do to ``T`` will be recorded and executed during the - :func:`glom` call. Take this example: - - >>> spec = T['a']['b']['c'] - >>> target = {'a': {'b': {'c': 'd'}}} - >>> glom(target, spec) - 'd' - - So far, we've relied on the ``'a.b.c'``-style shorthand for - access, or used the :class:`~glom.Path` objects, but if you want - to explicitly do attribute and key lookups, look no further than - ``T``. - - But T doesn't stop with unambiguous access. You can also call - methods and perform almost any action you would with a normal - object: - - >>> spec = ('a', (T['b'].items(), list)) # reviewed below - >>> glom(target, spec) - [('c', 'd')] - - A ``T`` object can go anywhere in the spec. As seen in the example - above, we access ``'a'``, use a ``T`` to get ``'b'`` and iterate - over its ``items``, turning them into a ``list``. - - You can even use ``T`` with :class:`~glom.Call` to construct objects: - - >>> class ExampleClass(object): - ... def __init__(self, attr): - ... self.attr = attr - ... - >>> target = {'attr': 3.14} - >>> glom(target, Call(ExampleClass, kwargs=T)).attr - 3.14 - - On a further note, while ``lambda`` works great in glom specs, and - can be very handy at times, ``T`` and :class:`~glom.Call` - eliminate the need for the vast majority of ``lambda`` usage with - glom. - - Unlike ``lambda`` and other functions, ``T`` roundtrips - beautifully and transparently: - - >>> T['a'].b['c']('success') - T['a'].b['c']('success') - - ``T``-related access errors raise a :exc:`~glom.PathAccessError` - during the :func:`~glom.glom` call. - - .. note:: - - While ``T`` is clearly useful, powerful, and here to stay, its - semantics are still being refined. Currently, operations beyond - method calls and attribute/item access are considered - experimental and should not be relied upon. - - """ - - __slots__ = ('__weakref__',) - - def __getattr__(self, name): - if name.startswith('__'): - raise AttributeError('T instances reserve dunder attributes') - return _t_child(self, '.', name) - - def __getitem__(self, item): - return _t_child(self, '[', item) - - def __call__(self, *args, **kwargs): - return _t_child(self, '(', (args, kwargs)) - - def __repr__(self): - t_path = _T_PATHS[self] - return _format_t(t_path[1:], t_path[0]) - - def __getstate__(self): - t_path = _T_PATHS[self] - return tuple(('T' if t_path[0] is T else 'S',) + t_path[1:]) - - def __setstate__(self, state): - _T_PATHS[self] = (T if state[0] == 'T' else S,) + state[1:]
- - -_T_PATHS = weakref.WeakKeyDictionary() - - -def _t_child(parent, operation, arg): - t = TType() - _T_PATHS[t] = _T_PATHS[parent] + (operation, arg) - return t - - -def _t_eval(target, _t, scope): - t_path = _T_PATHS[_t] - i = 1 - if t_path[0] is T: - cur = target - elif t_path[0] is S: - cur = scope - else: - raise ValueError('TType instance with invalid root object') - while i < len(t_path): - op, arg = t_path[i], t_path[i + 1] - if type(arg) in (Spec, TType, Literal): - arg = scope[glom](target, arg, scope) - if op == '.': - try: - cur = getattr(cur, arg) - except AttributeError as e: - raise PathAccessError(e, Path(_t), i // 2) - elif op == '[': - try: - cur = cur[arg] - except (KeyError, IndexError, TypeError) as e: - raise PathAccessError(e, Path(_t), i // 2) - elif op == 'P': - # Path type stuff (fuzzy match) - get = scope[TargetRegistry].get_handler( - 'get', cur, path=t_path[2 : i + 2 : 2] - ) - try: - cur = get(cur, arg) - except Exception as e: - raise PathAccessError(e, Path(_t), i // 2) - elif op == '(': - args, kwargs = arg - scope[Path] += t_path[2 : i + 2 : 2] - cur = scope[glom](target, Call(cur, args, kwargs), scope) - # call with target rather than cur, - # because it is probably more intuitive - # if args to the call "reset" their path - # e.g. "T.a" should mean the same thing - # in both of these specs: T.a and T.b(T.a) - i += 2 - return cur - - -T = TType() # target aka Mr. T aka "this" -S = TType() # like T, but means grab stuff from Scope, not Target - -_T_PATHS[T] = (T,) -_T_PATHS[S] = (S,) -UP = make_sentinel('UP') -ROOT = make_sentinel('ROOT') - - -def _format_invocation(name='', args=(), kwargs=None): # pragma: no cover - # TODO: add to boltons - kwargs = kwargs or {} - a_text = ', '.join([repr(a) for a in args]) - if isinstance(kwargs, dict): - kwarg_items = kwargs.items() - else: - kwarg_items = kwargs - kw_text = ', '.join(['%s=%r' % (k, v) for k, v in kwarg_items]) - - star_args_text = a_text - if star_args_text and kw_text: - star_args_text += ', ' - star_args_text += kw_text - - return '%s(%s)' % (name, star_args_text) - - -
[docs]class Let(object): - """ - This specifier type assigns variables to the scope. - - >>> target = {'data': {'val': 9}} - >>> spec = (Let(value=T['data']['val']), {'val': S['value']}) - >>> glom(target, spec) - {'val': 9} - """ - - def __init__(self, **kw): - if not kw: - raise TypeError('expected at least one keyword argument') - self._binding = kw - - def glomit(self, target, scope): - scope.update( - { - k: scope[glom](target, v, scope) - for k, v in self._binding.items() - } - ) - return target - - def __repr__(self): - cn = self.__class__.__name__ - return _format_invocation(cn, kwargs=self._binding)
- - -def _format_t(path, root=T): - def kwarg_fmt(kw): - if isinstance(kw, str): - return kw - return repr(kw) - - prepr = ['T' if root is T else 'S'] - i = 0 - while i < len(path): - op, arg = path[i], path[i + 1] - if op == '.': - prepr.append('.' + arg) - elif op == '[': - prepr.append('[%r]' % (arg,)) - elif op == '(': - args, kwargs = arg - prepr.append( - '(%s)' - % ', '.join( - [repr(a) for a in args] - + ['%s=%r' % (kwarg_fmt(k), v) for k, v in kwargs.items()] - ) - ) - elif op == 'P': - return _format_path(path) - i += 2 - return ''.join(prepr) - - -
[docs]class CheckError(GlomError): - """This :exc:`GlomError` subtype is raised when target data fails to - pass a :class:`Check`'s specified validation. - - An uncaught ``CheckError`` looks like this:: - - >>> target = {'a': {'b': 'c'}} - >>> glom(target, {'b': ('a.b', Check(type=int))}) # doctest: +SKIP - Traceback (most recent call last): - ... - glom.CheckError: target at path ['a.b'] failed check, got error: "expected type to be 'int', found type 'str'" - - - If the ``Check`` contains more than one condition, there may be - more than one error message. The string rendition of the - ``CheckError`` will include all messages. - - You can also catch the ``CheckError`` and programmatically access - messages through the ``msgs`` attribute on the ``CheckError`` - instance. - - .. note:: - - As of 2018-07-05 (glom v18.2.0), the validation subsystem is - still very new. Exact error message formatting may be enhanced - in future releases. - - """ - - def __init__(self, msgs, check, path): - self.msgs = msgs - self.check_obj = check - self.path = path - - def __repr__(self): - cn = self.__class__.__name__ - return '%s(%r, %r, %r)' % (cn, self.msgs, self.check_obj, self.path) - - def __str__(self): - msg = 'target at path %s failed check,' % self.path - if self.check_obj.spec is not T: - msg += ' subtarget at %r' % (self.check_obj.spec,) - if len(self.msgs) == 1: - msg += ' got error: %r' % (self.msgs[0],) - else: - msg += ' got %s errors: %r' % (len(self.msgs), self.msgs) - return msg
- - -RAISE = make_sentinel('RAISE') # flag object for "raise on check failure" - - -
[docs]class Check(object): - """Check objects are used to make assertions about the target data, - and either pass through the data or raise exceptions if there is a - problem. - - If any check condition fails, a :class:`~glom.CheckError` is raised. - - Args: - - spec: a sub-spec to extract the data to which other assertions will - be checked (defaults to applying checks to the target itself) - type: a type or sequence of types to be checked for exact match - equal_to: a value to be checked for equality match ("==") - validate: a callable or list of callables, each representing a - check condition. If one or more return False or raise an - exception, the Check will fail. - instance_of: a type or sequence of types to be checked with isinstance() - one_of: an iterable of values, any of which can match the target ("in") - default: an optional default value to replace the value when the check fails - (if default is not specified, GlomCheckError will be raised) - - Aside from *spec*, all arguments are keyword arguments. Each - argument, except for *default*, represent a check - condition. Multiple checks can be passed, and if all check - conditions are left unset, Check defaults to performing a basic - truthy check on the value. - - """ - - # TODO: the next level of Check would be to play with the Scope to - # allow checking to continue across the same level of - # dictionary. Basically, collect as many errors as possible before - # raising the unified CheckError. - def __init__(self, spec=T, **kwargs): - self.spec = spec - self._orig_kwargs = dict(kwargs) - self.default = kwargs.pop('default', RAISE) - - def _get_arg_val(name, cond, func, val, can_be_empty=True): - if val is _MISSING: - return () - if not is_iterable(val): - val = (val,) - elif not val and not can_be_empty: - raise ValueError( - 'expected %r argument to contain at least one value,' - ' not: %r' % (name, val) - ) - for v in val: - if not func(v): - raise ValueError( - 'expected %r argument to be %s, not: %r' - % (name, cond, v) - ) - return val - - # if there are other common validation functions, maybe a - # small set of special strings would work as valid arguments - # to validate, too. - def truthy(val): - return bool(val) - - validate = kwargs.pop('validate', _MISSING if kwargs else truthy) - type_arg = kwargs.pop('type', _MISSING) - instance_of = kwargs.pop('instance_of', _MISSING) - equal_to = kwargs.pop('equal_to', _MISSING) - one_of = kwargs.pop('one_of', _MISSING) - if kwargs: - raise TypeError('unexpected keyword arguments: %r' % kwargs.keys()) - - self.validators = _get_arg_val( - 'validate', 'callable', callable, validate - ) - self.instance_of = _get_arg_val( - 'instance_of', - 'a type', - lambda x: isinstance(x, type), - instance_of, - False, - ) - self.types = _get_arg_val( - 'type', 'a type', lambda x: isinstance(x, type), type_arg, False - ) - - if equal_to is not _MISSING: - self.vals = (equal_to,) - if one_of is not _MISSING: - raise TypeError( - 'expected "one_of" argument to be unset when' - ' "equal_to" argument is passed' - ) - elif one_of is not _MISSING: - if not is_iterable(one_of): - raise ValueError( - 'expected "one_of" argument to be iterable' - ' , not: %r' % one_of - ) - if not one_of: - raise ValueError( - 'expected "one_of" to contain at least' - ' one value, not: %r' % (one_of,) - ) - self.vals = one_of - else: - self.vals = () - return - - class _ValidationError(Exception): - 'for internal use inside of Check only' - pass - - def glomit(self, target, scope): - ret = target - errs = [] - if self.spec is not T: - target = scope[glom](target, self.spec, scope) - if self.types and type(target) not in self.types: - if self.default is not RAISE: - return self.default - errs.append( - 'expected type to be %r, found type %r' - % ( - self.types[0].__name__ - if len(self.types) == 1 - else tuple([t.__name__ for t in self.types]), - type(target).__name__, - ) - ) - - if self.vals and target not in self.vals: - if self.default is not RAISE: - return self.default - if len(self.vals) == 1: - errs.append( - 'expected {}, found {}'.format(self.vals[0], target) - ) - else: - errs.append( - 'expected one of {}, found {}'.format(self.vals, target) - ) - - if self.validators: - for i, validator in enumerate(self.validators): - try: - res = validator(target) - if res is False: - raise self._ValidationError - except Exception as e: - msg = 'expected %r check to validate target' % getattr( - validator, '__name__', None - ) or ('#%s' % i) - if type(e) is self._ValidationError: - if self.default is not RAISE: - return self.default - else: - msg += ' (got exception: %r)' % e - errs.append(msg) - - if self.instance_of and not isinstance(target, self.instance_of): - # TODO: can these early returns be done without so much copy-paste? - # (early return to avoid potentially expensive or even error-causeing - # string formats) - if self.default is not RAISE: - return self.default - errs.append( - 'expected instance of %r, found instance of %r' - % ( - self.instance_of[0].__name__ - if len(self.instance_of) == 1 - else tuple([t.__name__ for t in self.instance_of]), - type(target).__name__, - ) - ) - - if errs: - # TODO: due to the usage of basic path (not a Path - # object), the format can be a bit inconsistent here - # (e.g., 'a.b' and ['a', 'b']) - raise CheckError(errs, self, scope[Path]) - return ret - - def __repr__(self): - cn = self.__class__.__name__ - posargs = (self.spec,) if self.spec is not T else () - return format_invocation(cn, posargs, self._orig_kwargs)
- - -
[docs]class Auto(object): - """ - Switch to Auto mode (the default) - - TODO: this seems like it should be a sub-class of class Spec() -- - if Spec() could help define the interface for new "modes" or dialects - that would also help make match mode feel less duct-taped on - """ - - def __init__(self, spec=None): - self.spec = spec - - def glomit(self, target, scope): - scope[MODE] = _glom_auto - return scope[glom](target, self.spec, scope) - - def __repr__(self): - cn = self.__class__.__name__ - rpr = '' if self.spec is None else repr(self.spec) - return '%s(%s)' % (cn, rpr)
- - -class _AbstractIterable(_AbstractIterableBase): - __metaclass__ = ABCMeta - - @classmethod - def __subclasshook__(cls, C): - if C in (str, bytes): - return False - return callable(getattr(C, '__iter__', None)) - - -def _get_sequence_item(target, index): - return target[int(index)] - - -# handlers are 3-arg callables, with args (spec, target, scope) -# spec is the first argument for convenience in the case -# that the handler is a method of the spec type -def _handle_dict(target, spec, scope): - ret = type( - spec - )() # TODO: works for dict + ordereddict, but sufficient for all? - for field, subspec in spec.items(): - val = scope[glom](target, subspec, scope) - if val is SKIP: - continue - if type(field) in (Spec, TType): - field = scope[glom](target, field, scope) - ret[field] = val - return ret - - -def _handle_list(target, spec, scope): - subspec = spec[0] - iterate = scope[TargetRegistry].get_handler( - 'iterate', target, path=scope[Path] - ) - try: - iterator = iterate(target) - except Exception as e: - raise TypeError( - 'failed to iterate on instance of type %r at %r (got %r)' - % (target.__class__.__name__, Path(*scope[Path]), e) - ) - ret = [] - base_path = scope[Path] - for i, t in enumerate(iterator): - scope[Path] = base_path + [i] - val = scope[glom](t, subspec, scope) - if val is SKIP: - continue - if val is STOP: - break - ret.append(val) - return ret - - -def _handle_tuple(target, spec, scope): - res = target - for subspec in spec: - nxt = scope[glom](res, subspec, scope) - if nxt is SKIP: - continue - if nxt is STOP: - break - res = nxt - # this makes it so that specs in a tuple effectively nest. - scope = scope[LAST_CHILD_SCOPE] - if not isinstance(subspec, list): - scope[Path] += [getattr(subspec, '__name__', subspec)] - return res - - -
[docs]class TargetRegistry(object): - """ - responsible for registration of target types for iteration - and attribute walking - """ - - def __init__(self, register_default_types=True): - self._op_type_map = {} - self._op_type_tree = {} # see _register_fuzzy_type for details - - self._op_auto_map = ( - OrderedDict() - ) # op name to function that returns handler function - - self._register_builtin_ops() - - if register_default_types: - self._register_default_types() - return - -
[docs] def get_handler(self, op, obj, path=None, raise_exc=True): - """for an operation and object **instance**, obj, return the - closest-matching handler function, raising UnregisteredTarget - if no handler can be found for *obj* (or False if - raise_exc=False) - - """ - ret = False - obj_type = type(obj) - type_map = self.get_type_map(op) - if type_map: - try: - ret = type_map[obj_type] - except KeyError: - type_tree = self._op_type_tree.get(op, {}) - closest = self._get_closest_type(obj, type_tree=type_tree) - if closest is None: - ret = False - else: - ret = type_map[closest] - - if ret is False and raise_exc: - raise UnregisteredTarget( - op, obj_type, type_map=type_map, path=path - ) - - return ret
- - def get_type_map(self, op): - try: - return self._op_type_map[op] - except KeyError: - return OrderedDict() - - def _get_closest_type(self, obj, type_tree): - default = None - for cur_type, sub_tree in type_tree.items(): - if isinstance(obj, cur_type): - sub_type = self._get_closest_type(obj, type_tree=sub_tree) - ret = cur_type if sub_type is None else sub_type - return ret - return default - - def _register_default_types(self): - self.register(object) - self.register(dict, get=operator.getitem) - self.register(list, get=_get_sequence_item) - self.register(tuple, get=_get_sequence_item) - self.register(_AbstractIterable, iterate=iter) - - def _register_fuzzy_type(self, op, new_type, _type_tree=None): - """Build a "type tree", an OrderedDict mapping registered types to - their subtypes - - The type tree's invariant is that a key in the mapping is a - valid parent type of all its children. - - Order is preserved such that non-overlapping parts of the - subtree take precedence by which was most recently added. - """ - if _type_tree is None: - try: - _type_tree = self._op_type_tree[op] - except KeyError: - _type_tree = self._op_type_tree[op] = OrderedDict() - - registered = False - for cur_type, sub_tree in list(_type_tree.items()): - if issubclass(cur_type, new_type): - sub_tree = _type_tree.pop( - cur_type - ) # mutation for recursion brevity - try: - _type_tree[new_type][cur_type] = sub_tree - except KeyError: - _type_tree[new_type] = OrderedDict({cur_type: sub_tree}) - registered = True - elif issubclass(new_type, cur_type): - _type_tree[cur_type] = self._register_fuzzy_type( - op, new_type, _type_tree=sub_tree - ) - registered = True - if not registered: - _type_tree[new_type] = OrderedDict() - return _type_tree - - def register(self, target_type, **kwargs): - if not isinstance(target_type, type): - raise TypeError( - 'register expected a type, not an instance: %r' - % (target_type,) - ) - exact = kwargs.pop('exact', None) - new_op_map = dict(kwargs) - - for op_name in sorted( - set(self._op_auto_map.keys()) | set(new_op_map.keys()) - ): - cur_type_map = self._op_type_map.setdefault(op_name, OrderedDict()) - - if op_name in new_op_map: - handler = new_op_map[op_name] - elif target_type in cur_type_map: - handler = cur_type_map[target_type] - else: - try: - handler = self._op_auto_map[op_name](target_type) - except Exception as e: - raise TypeError( - 'error while determining support for operation' - ' "%s" on target type: %s (got %r)' - % (op_name, target_type.__name__, e) - ) - if handler is not False and not callable(handler): - raise TypeError( - 'expected handler for op "%s" to be' - ' callable or False, not: %r' % (op_name, handler) - ) - new_op_map[op_name] = handler - - for op_name, handler in new_op_map.items(): - self._op_type_map[op_name][target_type] = handler - - if not exact: - for op_name in new_op_map: - self._register_fuzzy_type(op_name, target_type) - - return - -
[docs] def register_op(self, op_name, auto_func=None, exact=False): - """add operations beyond the builtins ('get' and 'iterate' at the time - of writing). - - auto_func is a function that when passed a type, returns a - handler associated with op_name if it's supported, or False if - it's not. - - See glom.core.register_op() for the global version used by - extensions. - """ - if not isinstance(op_name, str): - raise TypeError( - 'expected op_name to be a text name, not: %r' % (op_name,) - ) - if auto_func is None: - auto_func = lambda t: False - elif not callable(auto_func): - raise TypeError( - 'expected auto_func to be callable, not: %r' % (auto_func,) - ) - - # determine support for any previously known types - known_types = set( - sum([list(m.keys()) for m in self._op_type_map.values()], []) - ) - type_map = self._op_type_map.get(op_name, OrderedDict()) - type_tree = self._op_type_tree.get(op_name, OrderedDict()) - for t in known_types: - if t in type_map: - continue - try: - handler = auto_func(t) - except Exception as e: - raise TypeError( - 'error while determining support for operation' - ' "%s" on target type: %s (got %r)' - % (op_name, t.__name__, e) - ) - if handler is not False and not callable(handler): - raise TypeError( - 'expected handler for op "%s" to be' - ' callable or False, not: %r' % (op_name, handler) - ) - type_map[t] = handler - - if not exact: - for t in known_types: - self._register_fuzzy_type(op_name, t, _type_tree=type_tree) - - self._op_type_map[op_name] = type_map - self._op_type_tree[op_name] = type_tree - self._op_auto_map[op_name] = auto_func
- - def _register_builtin_ops(self): - def _get_iterable_handler(type_obj): - return ( - iter - if callable(getattr(type_obj, '__iter__', None)) - else False - ) - - self.register_op('iterate', _get_iterable_handler) - self.register_op('get', lambda _: getattr)
- - -_DEFAULT_SCOPE = ChainMap({}) - - -
[docs]def glom(target, spec, **kwargs): - """Access or construct a value from a given *target* based on the - specification declared by *spec*. - - Accessing nested data, aka deep-get: - - >>> target = {'a': {'b': 'c'}} - >>> glom(target, 'a.b') - 'c' - - Here the *spec* was just a string denoting a path, - ``'a.b.``. As simple as it should be. The next example shows - how to use nested data to access many fields at once, and make - a new nested structure. - - Constructing, or restructuring more-complicated nested data: - - >>> target = {'a': {'b': 'c', 'd': 'e'}, 'f': 'g', 'h': [0, 1, 2]} - >>> spec = {'a': 'a.b', 'd': 'a.d', 'h': ('h', [lambda x: x * 2])} - >>> output = glom(target, spec) - >>> pprint(output) - {'a': 'c', 'd': 'e', 'h': [0, 2, 4]} - - ``glom`` also takes a keyword-argument, *default*. When set, - if a ``glom`` operation fails with a :exc:`GlomError`, the - *default* will be returned, very much like - :meth:`dict.get()`: - - >>> glom(target, 'a.xx', default='nada') - 'nada' - - The *skip_exc* keyword argument controls which errors should - be ignored. - - >>> glom({}, lambda x: 100.0 / len(x), default=0.0, skip_exc=ZeroDivisionError) - 0.0 - - Args: - target (object): the object on which the glom will operate. - spec (object): Specification of the output object in the form - of a dict, list, tuple, string, other glom construct, or - any composition of these. - default (object): An optional default to return in the case - an exception, specified by *skip_exc*, is raised. - skip_exc (Exception): An optional exception or tuple of - exceptions to ignore and return *default* (None if - omitted). If *skip_exc* and *default* are both not set, - glom raises errors through. - scope (dict): Additional data that can be accessed - via S inside the glom-spec. - - It's a small API with big functionality, and glom's power is - only surpassed by its intuitiveness. Give it a whirl! - - """ - # TODO: check spec up front - default = kwargs.pop('default', None if 'skip_exc' in kwargs else _MISSING) - skip_exc = kwargs.pop('skip_exc', () if default is _MISSING else GlomError) - scope = _DEFAULT_SCOPE.new_child( - { - Path: kwargs.pop('path', []), - Inspect: kwargs.pop('inspector', None), - MODE: _glom_auto, - } - ) - scope[UP] = scope - scope[ROOT] = scope - scope[T] = target - scope.update(kwargs.pop('scope', {})) - if kwargs: - raise TypeError('unexpected keyword args: %r' % sorted(kwargs.keys())) - try: - ret = _glom(target, spec, scope) - except skip_exc: - if default is _MISSING: - raise - ret = default - return ret
- - -def _glom(target, spec, scope): - parent = scope - scope = scope.new_child() - parent[LAST_CHILD_SCOPE] = scope - scope[T] = target - scope[Spec] = spec - scope[UP] = parent - - if isinstance(spec, TType): # must go first, due to callability - return _t_eval(target, spec, scope) - elif callable(getattr(spec, 'glomit', None)): - return spec.glomit(target, scope) - - return scope[MODE](target, spec, scope) - - -def _glom_auto(target, spec, scope): - if isinstance(spec, dict): - return _handle_dict(target, spec, scope) - elif isinstance(spec, list): - return _handle_list(target, spec, scope) - elif isinstance(spec, tuple): - return _handle_tuple(target, spec, scope) - elif isinstance(spec, str): - return Path.from_text(spec).glomit(target, scope) - elif callable(spec): - return spec(target) - - raise TypeError( - 'expected spec to be dict, list, tuple, callable, string,' - ' or other Spec-like type, not: %r' % (spec,) - ) - - -_DEFAULT_SCOPE.update( - {glom: _glom, TargetRegistry: TargetRegistry(register_default_types=True),} -) - - -
[docs]def register(target_type, **kwargs): - """Register *target_type* so :meth:`~Glommer.glom()` will - know how to handle instances of that type as targets. - - Args: - target_type (type): A type expected to appear in a glom() - call target - get (callable): A function which takes a target object and - a name, acting as a default accessor. Defaults to - :func:`getattr`. - iterate (callable): A function which takes a target object - and returns an iterator. Defaults to :func:`iter` if - *target_type* appears to be iterable. - exact (bool): Whether or not to match instances of subtypes - of *target_type*. - - .. note:: - - The module-level :func:`register()` function affects the - module-level :func:`glom()` function's behavior. If this - global effect is undesirable for your application, or - you're implementing a library, consider instantiating a - :class:`Glommer` instance, and using the - :meth:`~Glommer.register()` and :meth:`Glommer.glom()` - methods instead. - - """ - _DEFAULT_SCOPE[TargetRegistry].register(target_type, **kwargs) - return
- - -
[docs]def register_op(op_name, **kwargs): - """For extension authors needing to add operations beyond the builtin - 'get' and 'iterate' to the default scope. See TargetRegistry for more details. - """ - _DEFAULT_SCOPE[TargetRegistry].register_op(op_name, **kwargs) - return
- - -
[docs]class Glommer(object): - """All the wholesome goodness that it takes to make glom work. This - type mostly serves to encapsulate the type registration context so - that advanced uses of glom don't need to worry about stepping on - each other's toes. - - Glommer objects are lightweight and, once instantiated, provide - the :func:`glom()` method we know and love: - - >>> glommer = Glommer() - >>> glommer.glom({}, 'a.b.c', default='d') - 'd' - >>> Glommer().glom({'vals': list(range(3))}, ('vals', len)) - 3 - - Instances also provide :meth:`~Glommer.register()` method for - localized control over type handling. - - Args: - register_default_types (bool): Whether or not to enable the - handling behaviors of the default :func:`glom()`. These - default actions include dict access, list and iterable - iteration, and generic object attribute access. Defaults to - True. - """ - - def __init__(self, **kwargs): - register_default_types = kwargs.pop('register_default_types', True) - scope = kwargs.pop('scope', _DEFAULT_SCOPE) - - # this "freezes" the scope in at the time of construction - self.scope = ChainMap(dict(scope)) - self.scope[TargetRegistry] = TargetRegistry( - register_default_types=register_default_types - ) - -
[docs] def register(self, target_type, **kwargs): - """Register *target_type* so :meth:`~Glommer.glom()` will - know how to handle instances of that type as targets. - - Args: - target_type (type): A type expected to appear in a glom() - call target - get (callable): A function which takes a target object and - a name, acting as a default accessor. Defaults to - :func:`getattr`. - iterate (callable): A function which takes a target object - and returns an iterator. Defaults to :func:`iter` if - *target_type* appears to be iterable. - exact (bool): Whether or not to match instances of subtypes - of *target_type*. - - .. note:: - - The module-level :func:`register()` function affects the - module-level :func:`glom()` function's behavior. If this - global effect is undesirable for your application, or - you're implementing a library, consider instantiating a - :class:`Glommer` instance, and using the - :meth:`~Glommer.register()` and :meth:`Glommer.glom()` - methods instead. - - """ - exact = kwargs.pop('exact', False) - self.scope[TargetRegistry].register(target_type, exact=exact, **kwargs) - return
- - def glom(self, target, spec, **kwargs): - return glom(target, spec, scope=self.scope, **kwargs)
- - -
[docs]class Fill(object): - """A specifier type which switches to glom into "fill-mode". For the - spec contained within the Fill, glom will only interpret explicit - specifier types (including T objects). Whereas the default mode - has special interpretations for each of these builtins, fill-mode - takes a lighter touch, making Fill great for "filling out" Python - literals, like tuples, dicts, sets, and lists. - - >>> target = {'data': [0, 2, 4]} - >>> spec = Fill((T['data'][2], T['data'][0])) - >>> glom(target, spec) - (4, 0) - - As you can see, glom's usual built-in tuple item chaining behavior - has switched into a simple tuple constructor. - - (Sidenote for Lisp fans: Fill is like glom's quasi-quoting.) - - """ - - def __init__(self, spec=None): - self.spec = spec - - def glomit(self, target, scope): - scope[MODE] = _fill - return scope[glom](target, self.spec, scope) - - def fill(self, target): - return glom(target, self) - - def __repr__(self): - cn = self.__class__.__name__ - rpr = '' if self.spec is None else repr(self.spec) - return '%s(%s)' % (cn, rpr)
- - -def _fill(target, spec, scope): - # TODO: register an operator or two for the following to allow - # extension. This operator can probably be shared with the - # upcoming traversal/remap feature. - recurse = lambda val: scope[glom](target, val, scope) - if type(spec) is dict: - return {recurse(key): recurse(val) for key, val in spec.items()} - if type(spec) in (list, tuple, set, frozenset): - result = [recurse(val) for val in spec] - if type(spec) is list: - return result - return type(spec)(result) - if callable(spec): - return spec(target) - return spec -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/mappify.html b/docs/_modules/py2store/utils/mappify.html deleted file mode 100644 index 07e8d20..0000000 --- a/docs/_modules/py2store/utils/mappify.html +++ /dev/null @@ -1,321 +0,0 @@ - - - - - - - - py2store.utils.mappify — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.mappify

-"""
-Utils to wrap any object into a mapping interface
-"""
-from py2store.base import KvReader
-from py2store.utils.glom import glom, Path, Spec
-
-
-# TODO: Handle names_of_literals concern better. Here affects all keys with that name (regardless of parent context)
-#   See boltons remap: Has a path argument to carry the context
-# TODO: Probably should make this a class factory instead.
-# TODO: Might want the default to be caching iter (but need to be able to remove)
-# TODO:
-
[docs]class Mappify(KvReader): - """ - - >>> d = { - ... 'a': 'simple', - ... 'b': {'is': 'nested'}, - ... 'c': {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]} - ... } - >>> g = Mappify(d) - >>> - >>> assert list(g) == ['a', 'b.is', 'b', 'c.is', 'c.and', 'c.a', 'c'] - >>> assert g['a'] == 'simple' - >>> assert g['b.is'] == 'nested' - >>> assert g['c.a'] == [1, 2, 3] - >>> - >>> for k, v in g.items(): - ... print(f"{k}: {v}") - ... - a: simple - b.is: nested - b: {'is': 'nested'} - c.is: nested - c.and: has - c.a: [1, 2, 3] - c: {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]} - """ - - def __init__( - self, - target, - node_types=(dict,), - key_concat=lambda prefix, suffix: prefix + '.' + suffix, - names_of_literals=(), - **kwargs - ): - __doc__ = ( - 'A Mapping interface for glom. Fixes the target, and keys are considered as keys\n\n' - + str(glom.__doc__) - ) - self._target = target - self._node_types = node_types - self._key_concat = key_concat - self._names_of_literals = set(names_of_literals) - - self._kwargs = ( - kwargs # the stuff that is given to the **kwargs of glom - ) - # TODO: Not sure the following is kosher. Doesn't make me feel nice and fuzzy. - self._mk_similar_mappify = lambda x: self.__class__( - x, - key_concat=key_concat, - node_types=node_types, - names_of_literals=self._names_of_literals, - **kwargs - ) - - def __getitem__(self, spec): - return glom(self._target, spec, **self._kwargs) - - def __iter__(self): - """Depth first traversal: All nodes yielded.""" - for k in self._target: - val = self[Path(k)] - if isinstance(self[k], *self._node_types): - yield from ( - self._key_concat(k, nested_key) - for nested_key in self._mk_similar_mappify(val) - ) - yield k
- - -
[docs]class LeafMappify(Mappify): - """ - A dict-like interface to glom. Here, only leaf keys are taken into account. - - >>> d = { - ... 'a': 'simple', - ... 'b': {'is': 'nested'}, - ... 'c': {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]} - ... } - >>> g = LeafMappify(d) - >>> - >>> assert list(g) == ['a', 'b.is', 'c.is', 'c.and', 'c.a'] - >>> assert g['a'] == 'simple' - >>> assert g['b.is'] == 'nested' - >>> assert g['c.a'] == [1, 2, 3] - >>> - >>> for k, v in g.items(): - ... print(f"{k}: {v}") - ... - a: simple - b.is: nested - c.is: nested - c.and: has - c.a: [1, 2, 3] - """ - - def __iter__(self): - """Depth first traversal: Only leaf nodes yielded.""" - for k in self._target: - val = self[Path(k)] - if isinstance(val, *self._node_types): - yield from ( - self._key_concat(k, nested_key) - for nested_key in self._mk_similar_mappify(val) - ) - else: - yield k
- - -dot_str_key_iterator = lambda p: p.split('.') -bracket_getter = lambda obj, k: obj[k] - - -def simple_glom( - target, - spec, - node_types=(dict,), - key_iterator=dot_str_key_iterator, - item_getter=bracket_getter, -): - for k in key_iterator(spec): - print(k) - target = item_getter(target, k) - if not isinstance(target, node_types): - break - return target -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/mongoquery.html b/docs/_modules/py2store/utils/mongoquery.html deleted file mode 100644 index 5b79ba1..0000000 --- a/docs/_modules/py2store/utils/mongoquery.html +++ /dev/null @@ -1,554 +0,0 @@ - - - - - - - - py2store.utils.mongoquery — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.mongoquery

-"""
-Transform mongo-like selector dicts (filters) into boolean functions that implement the condition
-
-Modified from mongoquery (https://github.com/kapouille/mongoquery)
-
-mongoquery provides a straightforward API to match Python objects against
-MongoDB Query Language queries.
-"""
-
-import re
-from collections.abc import Sequence, Mapping
-
-string_types = (str,)
-
-
-
[docs]class QueryError(Exception): - """ Query error exception """ - - pass
- - -class _Undefined(object): - # pylint: disable=too-few-public-methods - pass - - -
[docs]def is_non_string_sequence(entry): - """ Returns True if entry is a Python sequence iterable, and not a string """ - return isinstance(entry, Sequence) and not isinstance(entry, str)
- - -
[docs]class Query(object): - """ The Query class is used to match an object against a MongoDB-like query """ - - # pylint: disable=too-few-public-methods - def __init__(self, definition): - self._definition = definition - -
[docs] def match(self, entry): - """ Matches the entry object against the query specified on instanciation """ - return self._match(self._definition, entry)
- - def _match(self, condition, entry): - if isinstance(condition, Mapping): - return all( - self._process_condition(sub_operator, sub_condition, entry) - for sub_operator, sub_condition in condition.items() - ) - if is_non_string_sequence(entry): - return condition in entry - return condition == entry - - def _extract(self, entry, path): - if not path: - return entry - if entry is None: - return entry - if is_non_string_sequence(entry): - try: - index = int(path[0]) - return self._extract(entry[index], path[1:]) - except ValueError: - return [self._extract(item, path) for item in entry] - elif isinstance(entry, Mapping) and path[0] in entry: - return self._extract(entry[path[0]], path[1:]) - else: - return _Undefined() - - def _path_exists(self, operator, condition, entry): - keys_list = list(operator.split('.')) - for i, k in enumerate(keys_list): - if isinstance(entry, Sequence) and not k.isdigit(): - for elem in entry: - operator = '.'.join(keys_list[i:]) - if ( - self._path_exists(operator, condition, elem) - == condition - ): - return condition - return not condition - elif isinstance(entry, Sequence): - k = int(k) - try: - entry = entry[k] - except (TypeError, IndexError, KeyError): - return not condition - return condition - - def _process_condition(self, operator, condition, entry): - if isinstance(condition, Mapping) and '$exists' in condition: - if isinstance(operator, string_types) and operator.find('.') != -1: - return self._path_exists(operator, condition['$exists'], entry) - elif condition['$exists'] != (operator in entry): - return False - elif tuple(condition.keys()) == ('$exists',): - return True - if isinstance(operator, str): - if operator.startswith('$'): - try: - return getattr(self, '_' + operator[1:])(condition, entry) - except AttributeError: - raise QueryError( - "{!r} operator isn't supported".format(operator) - ) - else: - try: - extracted_data = self._extract(entry, operator.split('.')) - except IndexError: - extracted_data = _Undefined() - else: - if operator not in entry: - return False - extracted_data = entry[operator] - return self._match(condition, extracted_data) - - ################## - # Common operators - ################## - - @staticmethod - def _not_implemented(*_): - raise NotImplementedError - - @staticmethod - def _noop(*_): - return True - - ###################### - # Comparison operators - ###################### - - @staticmethod - def _eq(condition, entry): - try: - return entry == condition - except TypeError: - return False - - @staticmethod - def _gt(condition, entry): - try: - return entry > condition - except TypeError: - return False - - @staticmethod - def _gte(condition, entry): - try: - return entry >= condition - except TypeError: - return False - - @staticmethod - def _in(condition, entry): - if is_non_string_sequence(condition): - for elem in condition: - if is_non_string_sequence(entry) and elem in entry: - return True - elif not is_non_string_sequence(entry) and elem == entry: - return True - return False - else: - raise TypeError('condition must be a list') - - @staticmethod - def _lt(condition, entry): - try: - return entry < condition - except TypeError: - return False - - @staticmethod - def _lte(condition, entry): - try: - return entry <= condition - except TypeError: - return False - - @staticmethod - def _ne(condition, entry): - return entry != condition - - def _nin(self, condition, entry): - return not self._in(condition, entry) - - ################### - # Logical operators - ################### - - def _and(self, condition, entry): - if isinstance(condition, Sequence): - return all( - self._match(sub_condition, entry) - for sub_condition in condition - ) - raise QueryError( - '$and has been attributed incorrect argument {!r}'.format( - condition - ) - ) - - def _nor(self, condition, entry): - if isinstance(condition, Sequence): - return all( - not self._match(sub_condition, entry) - for sub_condition in condition - ) - raise QueryError( - '$nor has been attributed incorrect argument {!r}'.format( - condition - ) - ) - - def _not(self, condition, entry): - return not self._match(condition, entry) - - def _or(self, condition, entry): - if isinstance(condition, Sequence): - return any( - self._match(sub_condition, entry) - for sub_condition in condition - ) - raise QueryError( - '$nor has been attributed incorrect argument {!r}'.format( - condition - ) - ) - - ################### - # Element operators - ################### - - @staticmethod - def _type(condition, entry): - # TODO: further validation to ensure the right type - # rather than just checking - bson_type = { - 1: float, - 2: str, - 3: Mapping, - 4: Sequence, - 5: bytearray, - 7: str, # object id (uuid) - 8: bool, - 9: str, # date (UTC datetime) - 10: type(None), - 11: str, # regex, - 13: str, # Javascript - 15: str, # JavaScript (with scope) - 16: int, # 32-bit integer - 17: int, # Timestamp - 18: int, # 64-bit integer - } - bson_alias = { - 'double': 1, - 'string': 2, - 'object': 3, - 'array': 4, - 'binData': 5, - 'objectId': 7, - 'bool': 8, - 'date': 9, - 'null': 10, - 'regex': 11, - 'javascript': 13, - 'javascriptWithScope': 15, - 'int': 16, - 'timestamp': 17, - 'long': 18, - } - - if condition == 'number': - return any( - [ - isinstance(entry, bson_type[bson_alias[alias]]) - for alias in ['double', 'int', 'long'] - ] - ) - - # resolves bson alias, or keeps original condition value - condition = bson_alias.get(condition, condition) - - if condition not in bson_type: - raise QueryError( - '$type has been used with unknown type {!r}'.format(condition) - ) - - return isinstance(entry, bson_type.get(condition)) - - _exists = _noop - - ###################### - # Evaluation operators - ###################### - - @staticmethod - def _mod(condition, entry): - return entry % condition[0] == condition[1] - - @staticmethod - def _regex(condition, entry): - if not isinstance(entry, str): - return False - try: - regex = re.match( - r'\A/(.+)/([imsx]{,4})\Z', condition, flags=re.DOTALL - ) - except TypeError: - raise QueryError( - '{!r} is not a regular expression ' - 'and should be a string'.format(condition) - ) - - flags = 0 - if regex: - options = regex.group(2) - for option in options: - flags |= getattr(re, option.upper()) - exp = regex.group(1) - else: - exp = condition - - try: - match = re.search(exp, entry, flags=flags) - except Exception as error: - raise QueryError( - '{!r} failed to execute with error {!r}'.format( - condition, error - ) - ) - return bool(match) - - _options = _text = _where = _not_implemented - - ################# - # Array operators - ################# - - def _all(self, condition, entry): - return all(self._match(item, entry) for item in condition) - - def _elemMatch(self, condition, entry): - # pylint: disable=invalid-name - if not isinstance(entry, Sequence): - return False - return any( - all( - self._process_condition(sub_operator, sub_condition, element) - for sub_operator, sub_condition in condition.items() - ) - for element in entry - ) - - @staticmethod - def _size(condition, entry): - if not isinstance(condition, int): - raise QueryError( - '$size has been attributed incorrect argument {!r}'.format( - condition - ) - ) - - if is_non_string_sequence(entry): - return len(entry) == condition - - return False - - #################### - # Comments operators - #################### - - _comment = _noop
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/signatures.html b/docs/_modules/py2store/utils/signatures.html deleted file mode 100644 index 7df3c0a..0000000 --- a/docs/_modules/py2store/utils/signatures.html +++ /dev/null @@ -1,1725 +0,0 @@ - - - - - - - - py2store.utils.signatures — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.signatures

-from functools import reduce
-from inspect import Signature, Parameter, signature
-from typing import Any, Union, Callable, Iterable
-from typing import Mapping as MappingType
-
-_empty = Parameter.empty
-empty = _empty
-
-_ParameterKind = type(Parameter(name='param_kind', kind=Parameter.POSITIONAL_OR_KEYWORD))
-ParamsType = Iterable[Parameter]
-ParamsAble = Union[ParamsType, MappingType[str, Parameter], Callable]
-SignatureAble = Union[Signature, Callable, ParamsType, MappingType[str, Parameter]]
-HasParams = Union[Iterable[Parameter], MappingType[str, Parameter], Signature, Callable]
-
-# short hands for Parameter kinds
-PK = Parameter.POSITIONAL_OR_KEYWORD
-VP, VK = Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD
-PO, KO = Parameter.POSITIONAL_ONLY, Parameter.KEYWORD_ONLY
-var_param_kinds = {VP, VK}
-var_param_types = var_param_kinds  # Deprecate: for back-compatibility. Delete in 2021
-
-
-# TODO: Couldn't make this work. See https://www.python.org/dev/peps/pep-0562/
-# deprecated_names = {'assure_callable', 'assure_signature', 'assure_params'}
-#
-#
-# def __getattr__(name):
-#     print(name)
-#     if name in deprecated_names:
-#         from warnings import warn
-#         warn(f"{name} is deprecated (see code for new name -- look for aliases)", DeprecationWarning)
-#     raise AttributeError(f"module {__name__} has no attribute {name}")
-
-
-def ensure_callable(obj: SignatureAble):
-    if isinstance(obj, Callable):
-        return obj
-    else:
-        def f(*args, **kwargs):
-            """Empty function made just to carry a signature"""
-
-        f.__signature__ = ensure_signature(obj)
-        return f
-
-
-assure_callable = ensure_callable  # alias for backcompatibility
-
-
-def ensure_signature(obj: SignatureAble):
-    if isinstance(obj, Signature):
-        return obj
-    elif isinstance(obj, Callable):
-        return Signature.from_callable(obj)
-    elif isinstance(obj, Iterable):
-        params = ensure_params(obj)
-        try:
-            return Signature(parameters=params)
-        except TypeError:
-            raise TypeError(f"Don't know how to make that object into a Signature: {obj}")
-    elif obj is None:
-        return Signature(parameters=())
-    # if you get this far...
-    raise TypeError(f"Don't know how to make that object into a Signature: {obj}")
-
-
-assure_signature = ensure_signature  # alias for backcompatibility
-
-
-def ensure_param(p):
-    if isinstance(p, Parameter):
-        return p
-    elif isinstance(p, dict):
-        return Parameter(**p)
-    elif isinstance(p, str):
-        return Parameter(name=p, kind=PK)
-    elif isinstance(p, Iterable):
-        name, *r = p
-        dflt_and_annotation = dict(zip(['default', 'annotation'], r))
-        return Parameter(name, PK, **dflt_and_annotation)
-    else:
-        raise TypeError(f"Don't know how to make {p} into a Parameter object")
-
-
-
[docs]def ensure_params(obj: ParamsAble = None): - """Get an interable of Parameter instances from an object. - - :param obj: - :return: - - From a callable: - - >>> def f(w, /, x: float = 1, y=1, *, z: int = 1): ... - >>> ensure_params(f) - [<Parameter "w">, <Parameter "x: float = 1">, <Parameter "y=1">, <Parameter "z: int = 1">] - - From an iterable of strings, dicts, or tuples - - >>> ensure_params(['xyz', - ... ('b', Parameter.empty, int), # if you want an annotation without a default use Parameter.empty - ... ('c', 2), # if you just want a default, make it the second element of your tuple - ... dict(name='d', kind=Parameter.VAR_KEYWORD)]) # all kinds are by default PK: Use dict to specify otherwise. - [<Parameter "xyz">, <Parameter "b: int">, <Parameter "c=2">, <Parameter "**d">] - - - If no input is given, an empty list is returned. - - >>> ensure_params() # equivalent to ensure_params(None) - [] - - """ - if obj is None: - return [] - elif isinstance(obj, Iterable): - if isinstance(obj, str): - obj = {'name': obj} - if isinstance(obj, Mapping): - obj = obj.values() - obj = list(obj) - if len(obj) == 0: - return obj - else: - return [ensure_param(p) for p in obj] - else: - if isinstance(obj, Parameter): - obj = Signature([obj]) - elif isinstance(obj, Callable): - obj = Signature.from_callable(obj) - elif obj is None: - obj = {} - if isinstance(obj, Signature): - return list(obj.parameters.values()) - # if function didn't return at this point, it didn't find a match, so raise - raise TypeError( - f"Don't know how to make that object into an iterable of inspect.Parameter objects: {obj}")
- - -assure_params = ensure_params # alias for backcompatibility - - -
[docs]class MissingArgValFor(object): - """A simple class to wrap an argument name, indicating that it was missing somewhere. - >>> MissingArgValFor('argname') - MissingArgValFor("argname") - """ - - def __init__(self, argname: str): - assert isinstance(argname, str) - self.argname = argname - - def __repr__(self): - return f'MissingArgValFor("{self.argname}")'
- - -# TODO: Look into the handling of the Parameter.VAR_KEYWORD kind in params -
[docs]def extract_arguments(params: ParamsAble, - *, - what_to_do_with_remainding='return', - include_all_when_var_keywords_in_params=False, - assert_no_missing_position_only_args=False, - **kwargs - ): - """Extract arguments needed to satisfy the params of a callable, dealing with the dirty details. - - Returns an (param_args, param_kwargs, remaining_kwargs) tuple where - - param_args are the values of kwargs that are PO (POSITION_ONLY) as defined by params, - - param_kwargs are those names that are both in params and not in param_args, and - - remaining_kwargs are the remaining. - - Intended usage: When you need to call a function `func` that has some position-only arguments, - but you have a kwargs dict of arguments in your hand. You can't just to `func(**kwargs)`. - But you can (now) do - ``` - args, kwargs, remaining = extract_arguments(kwargs, func) # extract from kwargs what you need for func - # ... check if remaing is empty (or not, depending on your paranoia), and then call the func: - func(*args, **kwargs) - ``` - (And if you doing that a lot: Do put it in a decorator!) - - See Also: extract_arguments.without_remainding - - The most frequent case you'll encounter is when there's no POSITION_ONLY args, your param_args will be empty - and you param_kwargs will contain all the arguments that match params, in the order of these params. - - >>> from inspect import signature - >>> def f(a, b, c=None, d=0): ... - >>> extract_arguments(f, b=2, a=1, c=3, d=4, extra='stuff') - ((), {'a': 1, 'b': 2, 'c': 3, 'd': 4}, {'extra': 'stuff'}) - - But sometimes you do have POSITION_ONLY arguments. - What extract_arguments will do for you is return the value of these as the first element of - the triple. - >>> def f(a, b, c=None, /, d=0): ... - >>> extract_arguments(f, b=2, a=1, c=3, d=4, extra='stuff') - ((1, 2, 3), {'d': 4}, {'extra': 'stuff'}) - - Note above how we get `(1, 2, 3)`, the order defined by the func's signature, - instead of `(2, 1, 3)`, the order defined by the kwargs. - So it's the params (e.g. function signature) that determine the order, not kwargs. - When using to call a function, this is especially crucial if we use POSITION_ONLY arguments. - - See also that the third output, the remaining_kwargs, as `{'extra': 'stuff'}` since - it was not in the params of the function. - Even if you include a VAR_KEYWORD kind of argument in the function, it won't change - this behavior. - - >>> def f(a, b, c=None, /, d=0, **kws): ... - >>> extract_arguments(f, b=2, a=1, c=3, d=4, extra='stuff') - ((1, 2, 3), {'d': 4}, {'extra': 'stuff'}) - - This is because we don't want to assume that all the kwargs can actually be - included in a call to the function behind the params. - Instead, the user can chose whether to include the remainder by doing a: - ``` - param_kwargs.update(remaining_kwargs) - ``` - et voilà. - - That said, we do understand that it may be a common pattern, so we'll do that extra step for you - if you specify `include_all_when_var_keywords_in_params=True`. - - >>> def f(a, b, c=None, /, d=0, **kws): ... - >>> extract_arguments(f, b=2, a=1, c=3, d=4, extra='stuff', - ... include_all_when_var_keywords_in_params=True) - ((1, 2, 3), {'d': 4, 'extra': 'stuff'}, {}) - - If you're expecting no remainder you might want to just get the args and kwargs (not this third - expected-to-be-empty remainder). You have two ways to do that, specifying: - `what_to_do_with_remainding='ignore'`, which will just return the (args, kwargs) pair - `what_to_do_with_remainding='assert_empty'`, which will do the same, but first assert the remainder is empty - We suggest to use `functools.partial` to configure the `argument_argument` you need. - - >>> from functools import partial - >>> arg_extractor = partial(extract_arguments, - ... what_to_do_with_remainding='assert_empty', - ... include_all_when_var_keywords_in_params=True) - >>> def f(a, b, c=None, /, d=0, **kws): ... - >>> arg_extractor(f, b=2, a=1, c=3, d=4, extra='stuff') - ((1, 2, 3), {'d': 4, 'extra': 'stuff'}) - - And what happens if the kwargs doesn't contain all the POSITION_ONLY arguments? - - >>> def f(a, b, c=None, /, d=0): ... - >>> extract_arguments(f, b=2, d='is a kw arg', e='is not an arg at all') - ((MissingArgValFor("a"), 2, MissingArgValFor("c")), {'d': 'is a kw arg'}, {'e': 'is not an arg at all'}) - - A few more examples... - - Let's call `extract_arguments` with params being not a function, - but, a Signature instance, a mapping whose values are Parameter instances, - or an iterable of Parameter instances... - - >>> def func(a, b, /, c=None, *, d=0, **kws): ... - >>> sig = Signature.from_callable(func) - >>> param_map = sig.parameters - >>> param_iterable = param_map.values() - >>> kwargs = dict(b=2, a=1, c=3, d=4, extra='stuff') - >>> assert extract_arguments(sig, **kwargs) == extract_arguments(func, **kwargs) - >>> assert extract_arguments(param_map, **kwargs) == extract_arguments(func, **kwargs) - >>> assert extract_arguments(param_iterable, **kwargs) == extract_arguments(func, **kwargs) - - Edge case: - No params specified? No problem. You'll just get empty args and kwargs. Everything in the remainder - >>> extract_arguments(params=(), b=2, a=1, c=3, d=0) - ((), {}, {'b': 2, 'a': 1, 'c': 3, 'd': 0}) - - :param params: Specifies what PO arguments should be extracted. - Could be a callable, Signature, iterable of Parameters... - :param what_to_do_with_remainding: - 'return' (default): function will return `param_args`, `param_kwargs`, `remaining_kwargs` - 'ignore': function will return `param_args`, `param_kwargs` - 'assert_empty': function will assert that `remaining_kwargs` is empty and then return `param_args`, `param_kwargs` - :param include_all_when_var_keywords_in_params=False, - :param assert_no_missing_position_only_args=False, - :param kwargs: The kwargs to extract the args from - :return: A (param_args, param_kwargs, remaining_kwargs) tuple. - """ - - assert what_to_do_with_remainding in {'return', 'ignore', 'assert_empty'} - assert isinstance(include_all_when_var_keywords_in_params, bool) - assert isinstance(assert_no_missing_position_only_args, bool) - - params = ensure_params(params) - if not params: - return (), {}, {k: v for k, v in kwargs.items()} - - params_names = tuple(p.name for p in params) - names_for_args = [p.name for p in params if p.kind == Parameter.POSITIONAL_ONLY] - param_kwargs_names = [x for x in params_names if x not in set(names_for_args)] - remaining_names = [x for x in kwargs if x not in params_names] - - param_args = tuple(kwargs.get(k, MissingArgValFor(k)) for k in names_for_args) - param_kwargs = {k: kwargs[k] for k in param_kwargs_names if k in kwargs} - remaining_kwargs = {k: kwargs[k] for k in remaining_names} - - if include_all_when_var_keywords_in_params: - if next((p.name for p in params if p.kind == Parameter.VAR_KEYWORD), None) is not None: - param_kwargs.update(remaining_kwargs) - remaining_kwargs = {} - - if assert_no_missing_position_only_args: - missing_argnames = tuple(x.argname for x in param_args if isinstance(x, MissingArgValFor)) - assert not missing_argnames, f"There were some missing positional only argnames: {missing_argnames}" - - if what_to_do_with_remainding == 'return': - return param_args, param_kwargs, remaining_kwargs - elif what_to_do_with_remainding == 'ignore': - return param_args, param_kwargs - elif what_to_do_with_remainding == 'assert_empty': - assert len(remaining_kwargs) == 0, f"remaining_kwargs not empty: remaining_kwargs={remaining_kwargs}" - return param_args, param_kwargs
- - -from functools import partial - -extract_arguments_ignoring_remainder = partial(extract_arguments, - what_to_do_with_remainding='ignore') -extract_arguments_asserting_no_remainder = partial(extract_arguments, - what_to_do_with_remainding='assert_empty') - -from collections.abc import Mapping -from typing import Optional, Iterable -from dataclasses import dataclass - - -
[docs]@dataclass -class Command: - """A dataclass that holds a `(caller, args, kwargs)` triple and allows one to execute `caller(*args, **kwargs)` - - :param caller: A callable that will be called with (*args, **kwargs) argument - :param args: A tuple - :param kwargs: - """ - caller: callable - args: Iterable = () - kwargs: Optional[dict] = None - - def __post_init__(self): - assert isinstance(self.args, Iterable) - self.kwargs = self.kwargs or {} - assert isinstance(self.kwargs, Mapping) - - def __call__(self): - return self.caller(*self.args, **self.kwargs)
- - -
[docs]def extract_commands(funcs, *, - mk_command: Callable[[Callable, tuple, dict], Any] = Command, - what_to_do_with_remainding='ignore', - **kwargs): - """ - - :param funcs: - :param mk_command: - :param kwargs: - :return: - - >>> def add(a, b: float = 0.0) -> float: - ... return a + b - >>> def mult(x: float, y=1): - ... return x * y - >>> def formula1(w, /, x: float, y=1, *, z: int = 1): - ... return ((w + x) * y) ** z - >>> commands = extract_commands((add, mult, formula1), a=1, b=2, c=3, d=4, e=5, w=6, x=7) - >>> for command in commands: - ... print(f"Calling {command.caller.__name__} with args={command.args} and kwargs={command.kwargs}") - ... print(command()) - ... - Calling add with args=() and kwargs={'a': 1, 'b': 2} - 3 - Calling mult with args=() and kwargs={'x': 7} - 7 - Calling formula1 with args=(6,) and kwargs={'x': 7} - 13 - """ - extract = partial(extract_arguments, - what_to_do_with_remainding=what_to_do_with_remainding, - include_all_when_var_keywords_in_params=False, - assert_no_missing_position_only_args=True) - - if callable(funcs): - funcs = [funcs] - - for func in funcs: - func_args, func_kwargs = extract(func, **kwargs) - yield mk_command(func, func_args, func_kwargs)
- - -
[docs]def commands_dict(funcs, *, - mk_command: Callable[[Callable, tuple, dict], Any] = Command, - what_to_do_with_remainding='ignore', - **kwargs): - """ - - :param funcs: - :param mk_command: - :param kwargs: - :return: - - >>> def add(a, b: float = 0.0) -> float: - ... return a + b - >>> def mult(x: float, y=1): - ... return x * y - >>> def formula1(w, /, x: float, y=1, *, z: int = 1): - ... return ((w + x) * y) ** z - >>> d = commands_dict((add, mult, formula1), a=1, b=2, c=3, d=4, e=5, w=6, x=7) - >>> d[add]() - 3 - >>> d[mult]() - 7 - >>> d[formula1]() - 13 - - """ - if callable(funcs): - funcs = [funcs] - it = extract_commands(funcs, what_to_do_with_remainding=what_to_do_with_remainding, - mk_command=mk_command, **kwargs) - return dict(zip(funcs, it))
- - -
[docs]class Param(Parameter): - # aliases - PK = Parameter.POSITIONAL_OR_KEYWORD - OP = Parameter.POSITIONAL_ONLY - OK = Parameter.KEYWORD_ONLY - VP = Parameter.VAR_POSITIONAL - VK = Parameter.VAR_KEYWORD - - def __init__(self, name, kind=PK, *, default=empty, annotation=empty): - super().__init__(name, kind, default=default, annotation=annotation)
- - -def param_has_default_or_is_var_kind(p: Parameter): - return p.default != Parameter.empty or p.kind in var_param_kinds - - -WRAPPER_UPDATES = ('__dict__',) - -from functools import wraps - - -# TODO: See other signature operating functions below in this module: -# Do we need them now that we have Sig? -# Do we want to keep them and have Sig use them? -
[docs]class Sig(Signature, Mapping): - """A subclass of inspect.Signature that has some extra api sugar, such as a dict-like interface, merging, ... - - You can construct a `Sig` object from a callable, - - >>> def f(w, /, x: float = 1, y=1, *, z: int = 1): ... - >>> Sig(f) - <Sig (w, /, x: float = 1, y=1, *, z: int = 1)> - - but also from any "ParamsAble" object. Such as... - an iterable of Parameter instances, strings, tuples, or dicts: - - >>> Sig(['a', ('b', Parameter.empty, int), ('c', 2), ('d', 1.0, float), - ... dict(name='special', kind=Parameter.KEYWORD_ONLY, default=0)]) - <Sig (a, b: int, c=2, d: float = 1.0, *, special=0)> - >>> - >>> Sig(['a', 'b', dict(name='args', kind=Parameter.VAR_POSITIONAL), - ... dict(name='kwargs', kind=Parameter.VAR_KEYWORD)] - ... ) - <Sig (a, b, *args, **kwargs)> - - The parameters of a signature are like a matrix whose rows are the parameters, - and the 4 columns are their properties: name, kind, default, and annotation - (the two laste ones being optional). - You get a row view when doing `Sig(...).parameters.values()`, - but what if you want a column-view? - Here's how: - - >>> def f(w, /, x: float = 1, y=2, *, z: int = 3): ... - >>> - >>> s = Sig(f) - >>> s.kinds # doctest: +NORMALIZE_WHITESPACE - {'w': <_ParameterKind.POSITIONAL_ONLY: 0>, - 'x': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>, - 'y': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>, - 'z': <_ParameterKind.KEYWORD_ONLY: 3>} - - >>> s.annotations - {'x': <class 'float'>, 'z': <class 'int'>} - >>> assert s.annotations == f.__annotations__ # same as what you get in `__annotations__` - >>> - >>> s.defaults - {'x': 1, 'y': 2, 'z': 3} - >>> # Note that it's not the same as you get in __defaults__ though: - >>> assert s.defaults != f.__defaults__ == (1, 2) # not 3, since __kwdefaults__ has that! - - We can sum (i.e. merge) and subtract (i.e. remove arguments) Sig instances. - Also, Sig instance is callable. It has the effect of inserting it's signature in the input - (in `__signature__`, but also inserting the resulting `__defaults__` and `__kwdefaults__`). - One of the intents is to be able to do things like: - - >>> import inspect - >>> def f(w, /, x: float = 1, y=1, *, z: int = 1): ... - >>> def g(i, w, j=2): ... - >>> - >>> @Sig.from_objs(f, g, ['a', ('b', 3.14), ('c', 42, int)]) - ... def some_func(*args, **kwargs): - ... ... - >>> inspect.signature(some_func) - <Signature (w, i, a, x: float = 1, y=1, z: int = 1, j=2, b=3.14, c: int = 42)> - >>> - >>> sig = Sig(f) + g + ['a', ('b', 3.14), ('c', 42, int)] - 'b' - ['a', 'z'] - >>> @sig - ... def some_func(*args, **kwargs): - ... ... - >>> inspect.signature(some_func) - <Signature (w, i, x: float = 1, y=1, j=2, c: int = 42)> - - """ - - def __init__(self, obj: ParamsAble = None, *, - return_annotation=empty, - __validate_parameters__=True, ): - """Initialize a Sig instance. - See Also: `ensure_params` to see what kind of objects you can make `Sig`s with. - - :param obj: A ParamsAble object, which could be: - - a callable, - - and iterable of Parameter instances - - an iterable of strings (representing annotation-less, default-less) argument names, - - tuples: (argname, default) or (argname, default, annotation), - - dicts: ``{'name': REQUIRED,...}`` with optional `kind`, `default` and `annotation` fields - - None (which will produce an argument-less Signature) - - >>> Sig(['a', 'b', 'c']) - <Sig (a, b, c)> - >>> Sig(['a', ('b', None), ('c', 42, int)]) # specifying defaults and annotations - <Sig (a, b=None, c: int = 42)> - >>> import inspect - >>> Sig(['a', ('b', inspect._empty, int)]) # specifying an annotation without a default - <Sig (a, b: int)> - >>> Sig(['a', 'b', 'c'], return_annotation=str) # specifying return annotation - <Sig (a, b, c) -> str> - - But you can always specify parameters the "long" way - - >>> Sig([inspect.Parameter(name='kws', kind=inspect.Parameter.VAR_KEYWORD)], return_annotation=str) - <Sig (**kws) -> str> - - And note that: - >>> Sig() - <Sig ()> - >>> Sig(None) - <Sig ()> - """ - if callable(obj) and return_annotation is empty: - return_annotation = Signature.from_callable(obj).return_annotation - super().__init__(ensure_params(obj), - return_annotation=return_annotation, - __validate_parameters__=__validate_parameters__) - -
[docs] def wrap(self, func: Callable): - """Gives the input function the signature. - This is similar to the `functools.wraps` function, but parametrized by a signature - (not a callable). Also, where as both write to the input func's `__signature__` - attribute, here we also write to - - `__defaults__` and `__kwdefaults__`, extracting these from `__signature__` - (functools.wraps doesn't do that at the time of writing this - (see https://github.com/python/cpython/pull/21379)). - - `__annotations__` (also extracted from `__signature__`) - - does not write to `__module__`, `__name__`, `__qualname__`, `__doc__` - (because again, we're basinig the injecton on a signature, not a function, - so we have no name, doc, etc...) - - >>> def f(w, /, x: float = 1, y=2, z: int = 3): - ... return w + x * y ** z - >>> f(0, 1) # 0 + 1 * 2 ** 3 - 8 - >>> f.__defaults__ - (1, 2, 3) - >>> assert 8 == f(0) == f(0, 1) == f(0, 1, 2) == f(0, 1, 2, 3) - - Now let's create a very similar function to f, but where: - - w is not position-only - - x annot is int instead of float, and doesn't have a default - - z's default changes to 10 - >>> def g(w, x: int, y=2, z: int = 10): - ... return w + x * y ** z - >>> s = Sig(g) - >>> f = s.wrap(f) - >>> import inspect - >>> inspect.signature(f) # see that - <Signature (w, x: int, y=2, z: int = 10)> - >>> # But (unlike with functools.wraps) here we get __defaults__ and __kwdefault__ - >>> f.__defaults__ # see that x has no more default, and z's default changed to 10 - (2, 10) - >>> f(0, 1) # see that now we get a different output because using different defaults - 1024 - - TODO: Something goes wrong when using keyword only arguments. - Note that the same problem occurs with functools.wraps, and even boltons.funcutils.wraps. - >>> def f(w, /, x: float = 1, y=2, *, z: int = 3): - ... return w + x * y ** z - >>> f(0) # 0 + 1 * 2 ** 3 - 8 - >>> f(0, 1, 2, 3) # error expected! - Traceback (most recent call last): - ... - TypeError: f() takes from 1 to 3 positional arguments but 4 were given - >>> def g(w, x: int, y=2, *, z: int = 10): - ... return w + x * y ** z - >>> s = Sig(g) - >>> f = s.wrap(f) - >>> f.__defaults__ - (2,) - >>> f.__kwdefaults__ - {'z': 10} - >>> f(0, 1, 2, 3) # error not expected! TODO: Make it work!! - Traceback (most recent call last): - ... - TypeError: f() takes from 2 to 3 positional arguments but 4 were given - """ - func.__signature__ = Signature(self.parameters.values(), - return_annotation=self.return_annotation) - func.__annotations__ = self.annotations - # endow the function with __defaults__ and __kwdefaults__ (not the default of functools.wraps!) - func.__defaults__, func.__kwdefaults__ = self._dunder_defaults_and_kwdefaults() - # "copy" over all other non-dunder attributes (not the default of functools.wraps!) - for attr in filter(lambda x: not x.startswith('__'), dir(func)): - setattr(func, attr, getattr(func, attr)) - return func
- - def __call__(self, func: Callable): - """Gives the input function the signature. - Just calls Sig.wrap so see docs of Sig.wrap (which contains examples and doctests). - """ - return self.wrap(func) - -
[docs] @classmethod - def sig_or_none(cls, obj): - """Returns a Sig instance, or None if there was a ValueError trying to construct it. - One use case is to be able to tell if an object has a signature or not. - - >>> has_signature = lambda obj: bool(Sig.sig_or_none(obj)) - >>> has_signature(print) - False - >>> has_signature(Sig) - True - - This means we can more easily get signatures in bulk without having to write try/catches: - - >>> len(list(filter(None, map(Sig.sig_or_none, (Sig, print, map, filter, Sig.wrap))))) - 2 - """ - try: - return (callable(obj) or None) and cls(obj) - except ValueError: - return None
- - def __bool__(self): - return True - - def _dunder_defaults_and_kwdefaults(self): - """Get the __defaults__, __kwdefaults__ (i.e. what would be the dunders baring these names in a python callable) - - >>> def foo(w, /, x: float, y=1, *, z: int = 1): ... - >>> __defaults__, __kwdefaults__ = Sig(foo)._dunder_defaults_and_kwdefaults() - >>> __defaults__ - (1,) - >>> __kwdefaults__ - {'z': 1} - """ - ko_names = self.names_for_kind(kind=KO) - dflts = self.defaults - return ( - tuple(dflts[name] for name in dflts if name not in ko_names), - # as known as __defaults__ in python callables - {name: dflts[name] for name in dflts if name in ko_names} # as known as __kwdefaults__ in python callables - ) - -
[docs] def to_signature_kwargs(self): - """The dict of keyword arguments to make this signature instance. - - >>> def f(w, /, x: float = 2, y=1, *, z: int = 0) -> float: ... - >>> Sig(f).to_signature_kwargs() # doctest: +NORMALIZE_WHITESPACE - {'parameters': - [<Parameter "w">, - <Parameter "x: float = 2">, - <Parameter "y=1">, - <Parameter "z: int = 0">], - 'return_annotation': <class 'float'>} - - Note that this does NOT return: - ``` - {'parameters': self.parameters, - 'return_annotation': self.return_annotation} - ``` - which would not actually work as keyword arguments of ``Signature``. - Yeah, I know. Don't ask me, ask the authors of `Signature`! - - Instead, `parammeters` will be ``list(self.parameters.values())``, which does work. - - """ - return {'parameters': list(self.parameters.values()), - 'return_annotation': self.return_annotation}
- -
[docs] def to_simple_signature(self): - """A builtin ``inspect.Signature`` instance equivalent (i.e. without the extra properties and methods) - - >>> def f(w, /, x: float = 2, y=1, *, z: int = 0): ... - >>> Sig(f).to_simple_signature() - <Signature (w, /, x: float = 2, y=1, *, z: int = 0)> - - """ - return Signature(**self.to_signature_kwargs())
- - @classmethod - def from_objs(cls, *objs, **name_and_dflts): - objs = list(objs) - for name, default in name_and_dflts.items(): - objs.append([{'name': name, 'kind': PK, 'default': default}]) - if len(objs) > 0: - first_obj, *objs = objs - sig = cls(ensure_params(first_obj)) - for obj in objs: - sig = sig + obj - return sig - else: # if no objs are given - return cls() # return an empty signature - - @classmethod - def from_params(cls, params): - if isinstance(params, Parameter): - params = (params,) - return cls(params) - - @property - def params(self): - """Just list(self.parameters.values()), because that's often what we want. - Why a Sig.params property when we already have a Sig.parameters property? - - Well, as much as is boggles my mind, it so happens that the Signature.parameters - is a name->Parameter mapping, but the Signature argument `parameters`, though baring the same name, - is expected to be a list of Parameter instances. - - So Sig.params is there to restore semantic consistence sanity. - """ - return list(self.parameters.values()) - - @property - def names(self): - return list(self.keys()) - - @property - def kinds(self): - return {p.name: p.kind for p in self.values()} - - @property - def defaults(self): - return {p.name: p.default for p in self.values() if p.default != Parameter.empty} - - @property - def annotations(self): - """{arg_name: annotation, ...} dict of annotations of the signature. - What `func.__annotations__` would give you. - """ - return {p.name: p.annotation for p in self.values() if p.annotation != Parameter.empty} - - # def substitute(self, **sub_for_name): - # def gen(): - # - # for name, substitution in sub_for_name.items(): - # - - def names_for_kind(self, kind): - return tuple(p.name for p in self.values() if p.kind == kind) - - def __iter__(self): - return iter(self.parameters) - - def __len__(self): - return len(self.parameters) - - def __getitem__(self, k): - return self.parameters[k] - - @property - def has_var_kinds(self): - return any(p.kind in var_param_kinds for p in set(self.values())) - - @property - def has_var_positional(self): - return any(p.kind == VP for p in list(self.values())) - - @property - def has_var_keyword(self): - return any(p.kind == VK for p in list(self.values())) - -
[docs] def merge_with_sig(self, sig: ParamsAble, ch_to_all_pk=False): - """Return a signature obtained by merging self signature with another signature. - Insofar as it can, given the kind precedence rules, the arguments of self will appear first. - - :param sig: The signature to merge with. - :param ch_to_all_pk: Whether to change all kinds of both signatures to PK (POSITIONAL_OR_KEYWORD) - :return: - - >>> from py2store.utils.signatures import Sig, KO - >>> - >>> def func(a=None, *, b=1, c=2): ... - ... - >>> - >>> s = Sig(func) - >>> s - <Sig (a=None, *, b=1, c=2)> - - Observe where the new arguments ``d`` and ``e`` are placed, - according to whether they have defaults and what their kind is: - - >>> s.merge_with_sig(['d', 'e']) - <Sig (d, e, a=None, *, b=1, c=2)> - >>> s.merge_with_sig(['d', ('e', 4)]) - <Sig (d, a=None, e=4, *, b=1, c=2)> - >>> s.merge_with_sig(['d', dict(name='e', kind=KO, default=4)]) - <Sig (d, a=None, *, b=1, c=2, e=4)> - >>> s.merge_with_sig([dict(name='d', kind=KO), dict(name='e', kind=KO, default=4)]) - <Sig (a=None, *, d, b=1, c=2, e=4)> - - If the kind of the params is not important, but order is, you can specify ``ch_to_all_pk=True``: - - >>> s.merge_with_sig(['d', 'e'], ch_to_all_pk=True) - <Sig (d, e, a=None, b=1, c=2)> - >>> s.merge_with_sig([('d', 3), ('e', 4)], ch_to_all_pk=True) - <Sig (a=None, b=1, c=2, d=3, e=4)> - - """ - if ch_to_all_pk: - _self = Sig(ch_signature_to_all_pk(self)) - _sig = Sig(ch_signature_to_all_pk(ensure_signature(sig))) - else: - _self = self - _sig = Sig(sig) - - _msg = f"\nHappened during an attempt to merge {self} and {sig}" - - assert not _self.has_var_keyword or not _sig.has_var_keyword, \ - f"Can't merge two signatures if they both have a VAR_POSITIONAL parameter:{_msg}" - assert not _self.has_var_keyword or not _sig.has_var_keyword, \ - "Can't merge two signatures if they both have a VAR_KEYWORD parameter:{_msg}" - assert all((_self[name].kind, _self[name].default) == (_sig[name].kind, _sig[name].default) - for name in _self.keys() & _sig.keys()), \ - f"During a signature merge, if two names are the same, they must have the same kind and default:{_msg}" - - params = list(self._chain_params_of_signatures( - _self.without_defaults, _sig.without_defaults, _self.with_defaults, _sig.with_defaults)) - params.sort(key=lambda p: p.kind) - return self.__class__(params)
- - def __add__(self, sig: ParamsAble): - """Merge two signatures (casting all non-VAR kinds to POSITIONAL_OR_KEYWORD before hand) - - Important Notes: - - The resulting Sig will loose it's return_annotation if it had one. - This is to avoid making too many assumptions about how the sig sum will be used. - If a return_annotation is needed (say, for composition, the last return_annotation - summed), one can subclass Sig and overwrite __add__ - - POSITION_ONLY and KEYWORD_ONLY kinds will be replaced by POSITIONAL_OR_KEYWORD kind. - This is to simplify the interface and code. - If the user really wants to maintain those kinds, they can replace them back after the fact. - - >>> def f(w, /, x: float = 1, y=1, *, z: int = 1): ... - >>> def h(i, j, w): ... # has a 'w' argument, like f and g - >>> def different(a, b: str, c=None): ... # No argument names in common with other functions - - >>> Sig(f) + Sig(different) - <Sig (w, a, b: str, x: float = 1, y=1, z: int = 1, c=None)> - >>> Sig(different) + Sig(f) - <Sig (a, b: str, w, c=None, x: float = 1, y=1, z: int = 1)> - - The order of the first signature will take precedence over the second, - but default-less arguments have to come before arguments with defaults. - first, and Note the difference of the orders. - >>> Sig(f) + Sig(h) - <Sig (w, i, j, x: float = 1, y=1, z: int = 1)> - >>> Sig(h) + Sig(f) - <Sig (i, j, w, x: float = 1, y=1, z: int = 1)> - - The sum of two Sig's takes a safe-or-blow-up-now approach. - If any of the arguments have different defaults or annotations, summing will raise an AssertionError. - It's up to the user to decorate their input functions to express the default they actually desire. - - >>> def ff(w, /, x: float, y=1, *, z: int = 1): ... # just like f, but without the default for x - >>> Sig(f) + Sig(ff) # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - AssertionError: During a signature merge, if two names are the same, they must have the same kind and default: - Happened during an attempt to merge (w, /, x: float = 1, y=1, *, z: int = 1) and (w, /, x: float, y=1, *, z: int = 1) - - - >>> def hh(i, j, w=1): ... # like h, but w has a default - >>> Sig(h) + Sig(hh) # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - AssertionError: During a signature merge, if two names are the same, they must have the same kind and default: - Happened during an attempt to merge (i, j, w) and (i, j, w=1) - - - >>> Sig(f) + ['w', ('y', 1), ('d', 1.0, float), - ... dict(name='special', kind=Parameter.KEYWORD_ONLY, default=0)] - <Sig (w, x: float = 1, y=1, z: int = 1, d: float = 1.0, special=0)> - - """ - return self.merge_with_sig(sig, ch_to_all_pk=True) - - def __radd__(self, sig: ParamsAble): - """Adding on the right. - The raison d'être for this is so that you can start your summing with any signature speccifying - object that Sig will be able to resolve into a signature. Like this: - - >>> ['first_arg', ('second_arg', 42)] + Sig(lambda x, y: x * y) - <Sig (first_arg, x, y, second_arg=42)> - - Note that the ``second_arg`` doesn't actually end up being the second argument because - it has a default and x and y don't. But if you did this: - - >>> ['first_arg', ('second_arg', 42)] + Sig(lambda x=0, y=1: x * y) - <Sig (first_arg, second_arg=42, x=0, y=1)> - - you'd get what you expect. - - Of course, we could have just obliged you to say ``Sig(['first_arg', ('second_arg', 42)])`` - explicitly and spare ourselves yet another method. - The reason we made ``__radd__`` is so we can make it handle 0 + Sig(...), so that you can - merge an iterable of signatures like this: - - >>> def f(a, b, c): ... - >>> def g(c, b, e): ... - >>> sigs = map(Sig, [f, g]) - >>> sum(sigs) - <Sig (a, b, c, e)> - - Let's say, for whatever reason (don't ask me), you wanted to make a function that contains all the - arguments of all the functions of ``os.path`` (that don't contain any var arg kinds). - - >>> import os.path - >>> funcs = list(filter(callable, (getattr(os.path, a) for a in dir(os.path) if not a.startswith('_')))) - >>> sigs = filter(lambda sig: not sig.has_var_kinds, map(Sig, funcs)) - >>> sum(sigs) - <Sig (path, p, paths, m, filename, s, f1, f2, fp1, fp2, s1, s2, start=None)> - """ - if sig == 0: # so that we can do ``sum(iterable_of_sigs)`` - sig = Sig([]) - else: - sig = Sig(sig) - return sig.merge_with_sig(self) - - def remove_names(self, names): - names = {p.name for p in ensure_params(names)} - new_params = {name: p for name, p in self.parameters.items() if name not in names} - return self.__class__(new_params, return_annotation=self.return_annotation) - - def __sub__(self, sig): - return self.remove_names(sig) - - @staticmethod - def _chain_params_of_signatures(*sigs): - """Yields Parameter instances taken from sigs without repeating the same name twice. - >>> str(list(Sig._chain_params_of_signatures(Sig(lambda x, *args, y=1: ...), - ... Sig(lambda x, y, z, **kwargs: ...)))) - '[<Parameter "x">, <Parameter "*args">, <Parameter "y=1">, <Parameter "z">, <Parameter "**kwargs">]' - """ - already_merged_names = set() - for s in sigs: - for p in s.parameters.values(): - if p.name not in already_merged_names: - yield p - already_merged_names.add(p.name) - - @property - def without_defaults(self): - """ - >>> from i2.signatures import Sig - >>> list(Sig(lambda *args, a, b, x=1, y=1, **kwargs: ...).without_defaults) - ['a', 'b'] - """ - return self.__class__(p for p in self.values() if not param_has_default_or_is_var_kind(p)) - - @property - def with_defaults(self): - """ - >>> from i2.signatures import Sig - >>> list(Sig(lambda *args, a, b, x=1, y=1, **kwargs: ...).with_defaults) - ['args', 'x', 'y', 'kwargs'] - """ - return self.__class__(p for p in self.values() if param_has_default_or_is_var_kind(p)) - - def normalize_kind(self): - def changed_params(): - for p in self.parameters.values(): - if p.kind not in var_param_kinds: - yield p.replace(kind=PK) - else: - yield p - - return self.__class__(list(changed_params()), return_annotation=self.return_annotation) - -
[docs] def kwargs_from_args_and_kwargs(self, args, kwargs, *, - apply_defaults=False, allow_partial=False, allow_excess=False, ignore_kind=False): - """Extracts a dict of input argument values for target signature, from args and kwargs. - - When you need to manage how the arguments of a function are specified, you need to take care of - multiple cases depending on whether they were specified as positional arguments - (`args`) or keyword arguments (`kwargs`). - - The `kwargs_from_args_and_kwargs` (and it's sorta-inverse inverse, `args_and_kwargs_from_kwargs`) - are there to help you manage this. - - If you could rely on the the fact that only `kwargs` were given it would reduce the complexity of your code. - This is why we have the `ch_signature_to_all_pk` function in `signatures.py`. - - We also need to have a means to make a `kwargs` only from the actual `(*args, **kwargs)` used at runtime. - We have `Signature.bind` (and `bind_partial`) for that. - - But these methods will fail if there is extra stuff in the `kwargs`. - Yet sometimes we'd like to have a `dict` that services several functions that will extract their needs from it. - - That's where `Sig.extract_kwargs(*args, **kwargs)` is needed. - :param args: The args the function will be called with. - :param kwargs: The kwargs the function will be called with. - :param apply_defaults: (bool) Whether to apply signature defaults to the non-specified argument names - :param allow_partial: (bool) True iff you want to allow partial signature fulfillment. - :param allow_excess: (bool) Set to True iff you want to allow extra kwargs items to be ignored. - :param ignore_kind: (bool) Set to True iff you want to ignore the position and keyword only kinds, - in order to be able to accept args and kwargs in such a way that there can be cross-over - (args that are supposed to be keyword only, and kwargs that are supposed to be positional only) - :return: An {argname: argval, ...} dict - - See also the sorta-inverse of this function: args_and_kwargs_from_kwargs - - >>> def foo(w, /, x: float, y='YY', *, z: str = 'ZZ'): ... - >>> sig = Sig(foo) - >>> assert ( - ... sig.kwargs_from_args_and_kwargs((11, 22, 'you'), dict(z='zoo')) - ... == sig.kwargs_from_args_and_kwargs((11, 22), dict(y='you', z='zoo')) - ... == {'w': 11, 'x': 22, 'y': 'you', 'z': 'zoo'}) - - By default, `apply_defaults=False`, which will lead to only get those arguments you input. - >>> sig.kwargs_from_args_and_kwargs(args=(11,), kwargs={'x': 22}) - {'w': 11, 'x': 22} - - But if you specify `apply_defaults=True` non-specified non-require arguments - will be returned with their defaults: - >>> sig.kwargs_from_args_and_kwargs(args=(11,), kwargs={'x': 22}, apply_defaults=True) - {'w': 11, 'x': 22, 'y': 'YY', 'z': 'ZZ'} - - By default, `ignore_excess=False`, so specifying kwargs that are not in the signature will lead to an exception. - >>> sig.kwargs_from_args_and_kwargs(args=(11,), kwargs={'x': 22, 'not_in_sig': -1}) - Traceback (most recent call last): - ... - TypeError: Got unexpected keyword arguments: not_in_sig - - Specifying `allow_excess=True` will ignore such excess fields of kwargs. - This is useful when you want to source several functions from a same dict. - >>> sig.kwargs_from_args_and_kwargs(args=(11,), kwargs={'x': 22, 'not_in_sig': -1}, allow_excess=True) - {'w': 11, 'x': 22} - - On the other side of `ignore_excess` you have `allow_partial` that will allow you, if - set to `True`, to underspecify the params of a function (in view of being completed later). - >>> sig.kwargs_from_args_and_kwargs(args=(), kwargs={'x': 22}) - Traceback (most recent call last): - ... - TypeError: missing a required argument: 'w' - - But if you specify `allow_partial=True`... - >>> sig.kwargs_from_args_and_kwargs(args=(), kwargs={'x': 22}, allow_partial=True) - {'x': 22} - - That's a lot of control (eight combinations total), but not everything is controllable here: - Position only and keyword only kinds need to be respected: - >>> sig.kwargs_from_args_and_kwargs(args=(1, 2, 3, 4), kwargs={}) - Traceback (most recent call last): - ... - TypeError: too many positional arguments - >>> sig.kwargs_from_args_and_kwargs(args=(), kwargs=dict(w=1, x=2, y=3, z=4)) - Traceback (most recent call last): - ... - TypeError: 'w' parameter is positional only, but was passed as a keyword - - But if you want to ignore the kind of parameter, just say so: - >>> sig.kwargs_from_args_and_kwargs(args=(1, 2, 3, 4), kwargs={}, ignore_kind=True) - {'w': 1, 'x': 2, 'y': 3, 'z': 4} - >>> sig.kwargs_from_args_and_kwargs(args=(), kwargs=dict(w=1, x=2, y=3, z=4), ignore_kind=True) - {'w': 1, 'x': 2, 'y': 3, 'z': 4} - """ - if ignore_kind: - sig = self.normalize_kind() - else: - sig = self - - no_var_kw = not sig.has_var_keyword - if no_var_kw: # has no var keyword kinds - sig_relevant_kwargs = {name: kwargs[name] for name in sig if name in kwargs} # take only what you need - else: - sig_relevant_kwargs = kwargs # take all the kwargs - - binder = sig.bind_partial if allow_partial else sig.bind - b = binder(*args, **sig_relevant_kwargs) - if apply_defaults: - b.apply_defaults() - - if no_var_kw and not allow_excess: # don't ignore excess kwargs - excess = kwargs.keys() - b.arguments - if excess: - excess_str = ', '.join(excess) - raise TypeError(f"Got unexpected keyword arguments: {excess_str}") - - return dict(b.arguments)
- -
[docs] def args_and_kwargs_from_kwargs(self, kwargs, - apply_defaults=False, allow_partial=False, allow_excess=False, ignore_kind=False): - """Get an (args, kwargs) tuple from the kwargs, where args contain the position only arguments. - - >>> def foo(w, /, x: float, y=1, *, z: int = 1): - ... return ((w + x) * y) ** z - >>> args, kwargs = Sig(foo).args_and_kwargs_from_kwargs(dict(w=4, x=3, y=2, z=1)) - >>> assert (args, kwargs) == ((4,), {'x': 3, 'y': 2, 'z': 1}) - >>> assert foo(*args, **kwargs) == foo(4, 3, 2, z=1) == 14 - - See kwargs_from_args_and_kwargs (namely for the description of the arguments. - """ - position_only_names = {p.name for p in self.parameters.values() if p.kind == PO} - args = tuple(kwargs[name] for name in position_only_names if name in kwargs) - # kwargs = self.kwargs_from_args_and_kwargs(args, kwargs, apply_defaults, allow_partial, allow_excess) - kwargs = {name: kwargs[name] for name in kwargs.keys() - position_only_names} - - kwargs = self.kwargs_from_args_and_kwargs(args, kwargs, apply_defaults=apply_defaults, - allow_partial=allow_partial, allow_excess=allow_excess, - ignore_kind=ignore_kind) - kwargs = {name: kwargs[name] for name in kwargs.keys() - position_only_names} - - return args, kwargs
- -
[docs] def extract_kwargs(self, *args, _ignore_kind=True, _allow_partial=False, _apply_defaults=False, **kwargs): - """Convenience method that calls kwargs_from_args_and_kwargs with defaults, and ignore_kind=True. - - Strict in the sense that the kwargs cannot contain any arguments that are not - valid argument names (as per the signature). - - >>> def foo(w, /, x: float, y='YY', *, z: str = 'ZZ'): ... - >>> sig = Sig(foo) - >>> assert ( - ... sig.extract_kwargs(1, 2, 3, z=4) - ... == sig.extract_kwargs(1, 2, y=3, z=4) - ... == {'w': 1, 'x': 2, 'y': 3, 'z': 4}) - - What about var positional and var keywords? - >>> def bar(*args, **kwargs): ... - >>> Sig(bar).extract_kwargs(1, 2, y=3, z=4) - {'args': (1, 2), 'kwargs': {'y': 3, 'z': 4}} - - Note that though `w` is a position only argument, you can specify `w=11` as a keyword argument too (by default): - >>> Sig(foo).extract_kwargs(w=11, x=22) - {'w': 11, 'x': 22} - - If you don't want to allow that, you can say `_ignore_kind=False` - >>> Sig(foo).extract_kwargs(w=11, x=22, _ignore_kind=False) - Traceback (most recent call last): - ... - TypeError: 'w' parameter is positional only, but was passed as a keyword - - You can use `_allow_partial` that will allow you, if - set to `True`, to underspecify the params of a function (in view of being completed later). - >>> Sig(foo).extract_kwargs(x=3, y=2) - Traceback (most recent call last): - ... - TypeError: missing a required argument: 'w' - - But if you specify `_allow_partial=True`... - >>> Sig(foo).extract_kwargs(x=3, y=2, _allow_partial=True) - {'x': 3, 'y': 2} - - By default, `_apply_defaults=False`, which will lead to only get those arguments you input. - >>> Sig(foo).extract_kwargs(4, x=3, y=2) - {'w': 4, 'x': 3, 'y': 2} - - But if you specify `_apply_defaults=True` non-specified non-require arguments - will be returned with their defaults: - >>> Sig(foo).extract_kwargs(4, x=3, y=2, _apply_defaults=True) - {'w': 4, 'x': 3, 'y': 2, 'z': 'ZZ'} - """ - return self.kwargs_from_args_and_kwargs( - args, kwargs, apply_defaults=_apply_defaults, allow_partial=_allow_partial, allow_excess=False, - ignore_kind=_ignore_kind)
- -
[docs] def extract_args_and_kwargs(self, *args, _ignore_kind=True, _allow_partial=False, _apply_defaults=False, **kwargs): - """Source the (args, kwargs) for the signature instance, ignoring excess arguments. - - >>> def foo(w, /, x: float, y=2, *, z: int = 1): - ... return w + x * y ** z - >>> args, kwargs = Sig(foo).extract_args_and_kwargs(4, x=3, y=2) - >>> (args, kwargs) == ((4,), {'x': 3, 'y': 2}) - True - - The difference with extract_kwargs is that here the output is ready to be called by the - function whose signature we have, since the position-only arguments will be returned as - args. - - >>> foo(*args, **kwargs) - 10 - - Note that though `w` is a position only argument, you can specify `w=4` as a keyword argument too (by default): - >>> args, kwargs = Sig(foo).extract_args_and_kwargs(w=4, x=3, y=2) - >>> (args, kwargs) == ((4,), {'x': 3, 'y': 2}) - True - - If you don't want to allow that, you can say `_ignore_kind=False` - >>> Sig(foo).extract_args_and_kwargs(w=4, x=3, y=2, _ignore_kind=False) - Traceback (most recent call last): - ... - TypeError: 'w' parameter is positional only, but was passed as a keyword - - You can use `_allow_partial` that will allow you, if - set to `True`, to underspecify the params of a function (in view of being completed later). - >>> Sig(foo).extract_args_and_kwargs(x=3, y=2) - Traceback (most recent call last): - ... - TypeError: missing a required argument: 'w' - - But if you specify `_allow_partial=True`... - >>> args, kwargs = Sig(foo).extract_args_and_kwargs(x=3, y=2, _allow_partial=True) - >>> (args, kwargs) == ((), {'x': 3, 'y': 2}) - True - - By default, `_apply_defaults=False`, which will lead to only get those arguments you input. - >>> args, kwargs = Sig(foo).extract_args_and_kwargs(4, x=3, y=2) - >>> (args, kwargs) == ((4,), {'x': 3, 'y': 2}) - True - - But if you specify `_apply_defaults=True` non-specified non-require arguments - will be returned with their defaults: - >>> args, kwargs = Sig(foo).extract_args_and_kwargs(4, x=3, y=2, _apply_defaults=True) - >>> (args, kwargs) == ((4,), {'x': 3, 'y': 2, 'z': 1}) - True - """ - kwargs = self.extract_kwargs(*args, _ignore_kind=_ignore_kind, _allow_partial=_allow_partial, - _apply_defaults=_apply_defaults, **kwargs) - return self.args_and_kwargs_from_kwargs(kwargs, allow_partial=_allow_partial, apply_defaults=_apply_defaults)
- -
[docs] def source_kwargs(self, *args, _ignore_kind=True, _allow_partial=False, _apply_defaults=False, **kwargs): - """Source the kwargs for the signature instance, ignoring excess arguments. - - >>> def foo(w, /, x: float, y='YY', *, z: str = 'ZZ'): ... - >>> Sig(foo).source_kwargs(11, x=22, extra='keywords', are='ignored') - {'w': 11, 'x': 22} - - Note that though `w` is a position only argument, you can specify `w=11` as a keyword argument too (by default): - >>> Sig(foo).source_kwargs(w=11, x=22, extra='keywords', are='ignored') - {'w': 11, 'x': 22} - - If you don't want to allow that, you can say `_ignore_kind=False` - >>> Sig(foo).source_kwargs(w=11, x=22, extra='keywords', are='ignored', _ignore_kind=False) - Traceback (most recent call last): - ... - TypeError: 'w' parameter is positional only, but was passed as a keyword - - You can use `_allow_partial` that will allow you, if - set to `True`, to underspecify the params of a function (in view of being completed later). - >>> Sig(foo).source_kwargs(x=3, y=2, extra='keywords', are='ignored') - Traceback (most recent call last): - ... - TypeError: missing a required argument: 'w' - - But if you specify `_allow_partial=True`... - >>> Sig(foo).source_kwargs(x=3, y=2, extra='keywords', are='ignored', _allow_partial=True) - {'x': 3, 'y': 2} - - By default, `_apply_defaults=False`, which will lead to only get those arguments you input. - >>> Sig(foo).source_kwargs(4, x=3, y=2, extra='keywords', are='ignored') - {'w': 4, 'x': 3, 'y': 2} - - But if you specify `_apply_defaults=True` non-specified non-require arguments - will be returned with their defaults: - >>> Sig(foo).source_kwargs(4, x=3, y=2, extra='keywords', are='ignored', _apply_defaults=True) - {'w': 4, 'x': 3, 'y': 2, 'z': 'ZZ'} - """ - return self.kwargs_from_args_and_kwargs( - args, kwargs, apply_defaults=_apply_defaults, allow_partial=_allow_partial, allow_excess=True, - ignore_kind=_ignore_kind)
- -
[docs] def source_args_and_kwargs(self, *args, _ignore_kind=True, _allow_partial=False, _apply_defaults=False, **kwargs): - """Source the (args, kwargs) for the signature instance, ignoring excess arguments. - - >>> def foo(w, /, x: float, y=2, *, z: int = 1): - ... return w + x * y ** z - >>> args, kwargs = Sig(foo).source_args_and_kwargs(4, x=3, y=2, extra='keywords', are='ignored') - >>> assert (args, kwargs) == ((4,), {'x': 3, 'y': 2}) - >>> - - The difference with source_kwargs is that here the output is ready to be called by the - function whose signature we have, since the position-only arguments will be returned as - args. - - >>> foo(*args, **kwargs) - 10 - - Note that though `w` is a position only argument, you can specify `w=4` as a keyword argument too (by default): - >>> args, kwargs = Sig(foo).source_args_and_kwargs(w=4, x=3, y=2, extra='keywords', are='ignored') - >>> assert (args, kwargs) == ((4,), {'x': 3, 'y': 2}) - - If you don't want to allow that, you can say `_ignore_kind=False` - >>> Sig(foo).source_args_and_kwargs(w=4, x=3, y=2, extra='keywords', are='ignored', _ignore_kind=False) - Traceback (most recent call last): - ... - TypeError: 'w' parameter is positional only, but was passed as a keyword - - You can use `_allow_partial` that will allow you, if - set to `True`, to underspecify the params of a function (in view of being completed later). - >>> Sig(foo).source_args_and_kwargs(x=3, y=2, extra='keywords', are='ignored') - Traceback (most recent call last): - ... - TypeError: missing a required argument: 'w' - - But if you specify `_allow_partial=True`... - >>> args, kwargs = Sig(foo).source_args_and_kwargs(x=3, y=2, extra='keywords', are='ignored', _allow_partial=True) - >>> (args, kwargs) == ((), {'x': 3, 'y': 2}) - True - - By default, `_apply_defaults=False`, which will lead to only get those arguments you input. - >>> args, kwargs = Sig(foo).source_args_and_kwargs(4, x=3, y=2, extra='keywords', are='ignored') - >>> (args, kwargs) == ((4,), {'x': 3, 'y': 2}) - True - - But if you specify `_apply_defaults=True` non-specified non-require arguments - will be returned with their defaults: - >>> args, kwargs = Sig(foo).source_args_and_kwargs(4, x=3, y=2, extra='keywords', are='ignored', _apply_defaults=True) - >>> (args, kwargs) == ((4,), {'x': 3, 'y': 2, 'z': 1}) - True - """ - kwargs = self.kwargs_from_args_and_kwargs(args, kwargs, allow_excess=True, ignore_kind=_ignore_kind, - allow_partial=_allow_partial, apply_defaults=_apply_defaults) - return self.args_and_kwargs_from_kwargs(kwargs, allow_excess=True, ignore_kind=_ignore_kind, - allow_partial=_allow_partial, apply_defaults=_apply_defaults)
- - -######################################################################################################################## -# Recipes - -
[docs]def mk_sig_from_args(*args_without_default, **args_with_defaults): - """Make a Signature instance by specifying args_without_default and args_with_defaults. - >>> mk_sig_from_args('a', 'b', c=1, d='bar') - <Signature (a, b, c=1, d='bar')> - """ - assert all(isinstance(x, str) for x in args_without_default), "all default-less arguments must be strings" - return Sig.from_objs(*args_without_default, **args_with_defaults).to_simple_signature()
- - -
[docs]def call_forgivingly(func, *args, **kwargs): - """Call function on giben args and kwargs, but only taking what the function needs - (not choking if they're extras variables)""" - args, kwargs = Sig(func).source_args_and_kwargs(*args, **kwargs) - return func(*args, **kwargs)
- - -def has_signature(obj): - return bool(Sig.sig_or_none(obj)) - - -def number_of_required_arguments(obj): - sig = Sig(obj) - return len(sig) - len(sig.defaults) - - -######################################################################################################################## -# TODO: Encorporate in Sig -
[docs]def insert_annotations(s: Signature, *, return_annotation=empty, **annotations): - """Insert annotations in a signature. - (Note: not really insert but returns a copy of input signature) - >>> from inspect import signature - >>> s = signature(lambda a, b, c=1, d='bar': 0) - >>> s - <Signature (a, b, c=1, d='bar')> - >>> ss = insert_annotations(s, b=int, d=str) - >>> ss - <Signature (a, b: int, c=1, d: str = 'bar')> - >>> insert_annotations(s, b=int, d=str, e=list) # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - AssertionError: These argument names weren't found in the signature: {'e'} - """ - assert set(annotations) <= set(s.parameters), \ - f"These argument names weren't found in the signature: {set(annotations) - set(s.parameters)}" - params = dict(s.parameters) - for name, annotation in annotations.items(): - p = params[name] - params[name] = Parameter(name=name, kind=p.kind, default=p.default, annotation=annotation) - return Signature(params.values(), return_annotation=return_annotation)
- - -
[docs]def common_and_diff_argnames(func1: callable, func2: callable) -> dict: - """Get list of argument names that are common to two functions, as well as the two lists of names that are different - - Args: - func1: First function - func2: Second function - - Returns: A dict with fields 'common', 'func1_not_func2', and 'func2_not_func1' - - >>> def f(t, h, i, n, k): ... - >>> def g(t, w, i, c, e): ... - >>> common_and_diff_argnames(f, g) - {'common': ['t', 'i'], 'func1_not_func2': ['h', 'n', 'k'], 'func2_not_func1': ['w', 'c', 'e']} - >>> common_and_diff_argnames(g, f) - {'common': ['t', 'i'], 'func1_not_func2': ['w', 'c', 'e'], 'func2_not_func1': ['h', 'n', 'k']} - """ - p1 = signature(func1).parameters - p2 = signature(func2).parameters - return { - 'common': [x for x in p1 if x in p2], - 'func1_not_func2': [x for x in p1 if x not in p2], - 'func2_not_func1': [x for x in p2 if x not in p1], - }
- - -dflt_name_for_kind = { - Parameter.VAR_POSITIONAL: 'args', - Parameter.VAR_KEYWORD: 'kwargs', -} - -arg_order_for_param_tuple = ('name', 'default', 'annotation', 'kind') - - -
[docs]def set_signature_of_func(func, parameters, *, return_annotation=empty, __validate_parameters__=True): - """Set the signature of a function, with sugar. - - Args: - func: Function whose signature you want to set - signature: A list of parameter specifications. This could be an inspect.Parameter object or anything that - the mk_param function can resolve into an inspect.Parameter object. - return_annotation: Passed on to inspect.Signature. - __validate_parameters__: Passed on to inspect.Signature. - - Returns: - None (but sets the signature of the input function) - - >>> import inspect - >>> def foo(*args, **kwargs): - ... pass - ... - >>> inspect.signature(foo) - <Signature (*args, **kwargs)> - >>> set_signature_of_func(foo, ['a', 'b', 'c']) - >>> inspect.signature(foo) - <Signature (a, b, c)> - >>> set_signature_of_func(foo, ['a', ('b', None), ('c', 42, int)]) # specifying defaults and annotations - >>> inspect.signature(foo) - <Signature (a, b=None, c: int = 42)> - >>> set_signature_of_func(foo, ['a', 'b', 'c'], return_annotation=str) # specifying return annotation - >>> inspect.signature(foo) - <Signature (a, b, c) -> str> - >>> # But you can always specify parameters the "long" way - >>> set_signature_of_func( - ... foo, - ... [inspect.Parameter(name='kws', kind=inspect.Parameter.VAR_KEYWORD)], return_annotation=str) - >>> inspect.signature(foo) - <Signature (**kws) -> str> - - """ - sig = Sig(parameters, - return_annotation=return_annotation, - __validate_parameters__=__validate_parameters__) - func.__signature__ = sig.to_simple_signature()
- # Not returning func so it's clear(er) that the function is transformed in place - - -############# Tools for testing ######################################################################################## -from functools import partial - - -
[docs]def param_for_kind(name=None, kind='positional_or_keyword', with_default=False, annotation=Parameter.empty): - """Function to easily and flexibly make inspect.Parameter objects for testing. - - It's annoying to have to compose parameters from scratch to testing things. - This tool should help making it less annoying. - - >>> from i2.signatures import param_kinds - >>> list(map(param_for_kind, param_kinds)) - [<Parameter "POSITIONAL_ONLY">, <Parameter "POSITIONAL_OR_KEYWORD">, <Parameter "VAR_POSITIONAL">, <Parameter "KEYWORD_ONLY">, <Parameter "VAR_KEYWORD">] - >>> param_for_kind.positional_or_keyword() - <Parameter "POSITIONAL_OR_KEYWORD"> - >>> param_for_kind.positional_or_keyword('foo') - <Parameter "foo"> - >>> param_for_kind.keyword_only() - <Parameter "KEYWORD_ONLY"> - >>> param_for_kind.keyword_only('baz', with_default=True) - <Parameter "baz='dflt_keyword_only'"> - """ - name = name or f"{kind}" - kind_obj = getattr(Parameter, str(kind).upper()) - kind = str(kind_obj).lower() - default = f"dflt_{kind}" if with_default and kind not in {'var_positional', 'var_keyword'} else Parameter.empty - return Parameter(name=name, - kind=kind_obj, - default=default, - annotation=annotation)
- - -param_kinds = list(filter(lambda x: x.upper() == x, Parameter.__dict__)) - -for kind in param_kinds: - lower_kind = kind.lower() - setattr(param_for_kind, lower_kind, - partial(param_for_kind, kind=kind)) - setattr(param_for_kind, 'with_default', - partial(param_for_kind, with_default=True)) - setattr(getattr(param_for_kind, lower_kind), 'with_default', - partial(param_for_kind, kind=kind, with_default=True)) - setattr(getattr(param_for_kind, 'with_default'), lower_kind, - partial(param_for_kind, kind=kind, with_default=True)) - - -def ch_signature_to_all_pk(sig): - def changed_params(): - for p in sig.parameters.values(): - if p.kind not in var_param_kinds: - yield p.replace(kind=PK) - else: - yield p - - return Signature(list(changed_params()), return_annotation=sig.return_annotation) -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/sliceable.html b/docs/_modules/py2store/utils/sliceable.html deleted file mode 100644 index 89410c0..0000000 --- a/docs/_modules/py2store/utils/sliceable.html +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - py2store.utils.sliceable — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.sliceable

-"""
-utils to add sliceable functionality to stores
-"""
-from itertools import islice
-from collections.abc import Mapping
-
-
-
[docs]class iSliceStore(Mapping): - """ - Wraps a store to make a reader that acts as if the store was a list (with integer keys, and that can be sliced). - I say "list", but it should be noted that the behavior is more that of range, that outputs an element of the list - when keying with an integer, but returns an iterable object (a range) if sliced. - - Here, a map object is returned when the sliceable store is sliced. - - >>> s = {'foo': 'bar', 'hello': 'world', 'alice': 'bob'} - >>> sliceable_s = iSliceStore(s) - >>> sliceable_s[1] - 'world' - >>> list(sliceable_s[0:2]) - ['bar', 'world'] - >>> list(sliceable_s[-2:]) - ['world', 'bob'] - >>> list(sliceable_s[:-1]) - ['bar', 'world'] - """ - - def __init__(self, store): - self.store = store - - def _get_key(self, k): - return next(islice(self.store.keys(), k, k + 1)) - - def _get_keys(self, k): - start, stop, step = k.start, k.stop, k.step - assert (step is None) or (step > 0), "step of slice can't be negative" - - negative_start = start is not None and start < 0 - negative_stop = stop is not None and stop < 0 - if negative_start or negative_stop: - n = self.__len__() - if negative_start: - start = n + start - if negative_stop: - stop = n + stop - - return islice(self.store.keys(), start, stop, step) - - def __getitem__(self, k): - if not isinstance(k, slice): - _id = next(islice(self.store.keys(), k, k + 1)) - return self.store[self._get_key(k)] - else: - return map(self.store.__getitem__, self._get_keys(k)) - - def __iter__(self): - return self.store.__iter__() - - def __len__(self): - return self.store.__len__()
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/timeseries_caching.html b/docs/_modules/py2store/utils/timeseries_caching.html deleted file mode 100644 index f261ca2..0000000 --- a/docs/_modules/py2store/utils/timeseries_caching.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - py2store.utils.timeseries_caching — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.timeseries_caching

-"""Tools to cache time-series data.
-"""
-
-from collections import deque
-from py2store.utils.affine_conversion import get_affine_converter_and_inverse
-
-
-
[docs]class RegularTimeseriesCache: - """ - A type that pretends to be a (possibly very large) list, but where contents of the list are populated as they are - needed. Further, the indexing of the list can be overwritten for the convenience of the user. - - The canonical application is where we have segments of continuous waveform indexed by utc microseconds timestamps. - - It is convenient to be able to read segments of this waveform as if it was one big waveform (handling the - discontinuities gracefully), and have the choice of using (relative or absolute) integer indices or utc indices. - """ - - def __init__(self, data_rate=1, time_rate=1, maxlen=None): - self.buffer = deque(iterable=(), maxlen=maxlen) - self.data_rate = data_rate - self.time_rate = time_rate - self.time_per_data = self.time_rate / self.data_rate - self.data_per_time = self.data_rate / self.time_rate - self.bt = None - self.tt = None - - def time_to_idx(self, t): - return (t - self.bt) * self.data_per_time - - def idx_to_time(self, idx): - return idx * self.time_per_data + self.bt - - def update(self, bt): - pass - - def __getitem__(self, item): - if isinstance(item, slice): - start = item.start
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/py2store/utils/uri_utils.html b/docs/_modules/py2store/utils/uri_utils.html deleted file mode 100644 index ed3e05d..0000000 --- a/docs/_modules/py2store/utils/uri_utils.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - - - - py2store.utils.uri_utils — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for py2store.utils.uri_utils

-"""
-utils to work with URIs
-"""
-from urllib.parse import urlsplit
-
-
-
[docs]def parse_uri(uri): - """ - Parses DB URI string into a dict of params. - :param uri: string formatted as: "scheme://username:password@host:port/database" - :return: a dict with these params parsed. - """ - splitted_uri = urlsplit(uri) - - if splitted_uri.path.startswith('/'): - path = splitted_uri.path[1:] - else: - path = '' - - return { - 'scheme': splitted_uri.scheme, - 'database': path, - 'username': splitted_uri.username, - 'password': splitted_uri.password, - 'hostname': splitted_uri.hostname, - 'port': splitted_uri.port, - }
- - -
[docs]def build_uri( - scheme, - database='', # TODO: Change name: Not always a database - username=None, - password=None, - host='localhost', - port=None, -): - """ - Reverse of `parse_uri` function. - Builds a URI string from provided params. - """ - port_ = f':{port}' if port else '' - uri = f'{scheme}://{username}:{password}@{host}{port_}/{database}' - return uri
- - -import string -from py2store.signatures import set_signature_of_func - -str_formatter = string.Formatter() - - -def mk_str_making_func( - str_format: str, input_trans=None, method=False, module=None, name=None -): - fields = tuple( - filter(None, (x[1] for x in str_formatter.parse(str_format))) - ) # TODO: validate - n_fields = len(fields) - - if method: - - def _mk(self, *args, **kwargs): - n = len(args) + len(kwargs) - if n > n_fields: - raise ValueError( - f'You have too many arguments: (args, kwargs) is ({args}, {kwargs})' - ) - elif n < n_fields: - raise ValueError( - f'You have too few arguments: (args, kwargs) is ({args}, {kwargs})' - ) - kwargs = dict({k: v for k, v in zip(fields, args)}, **kwargs) - if input_trans is not None: - kwargs = input_trans(**kwargs) - return str_format.format(**kwargs) - - set_signature_of_func(_mk, ['self'] + list(fields)) - else: - - def _mk(*args, **kwargs): - n = len(args) + len(kwargs) - if n > n_fields: - raise ValueError( - f'You have too many arguments: (args, kwargs) is ({args}, {kwargs})' - ) - elif n < n_fields: - raise ValueError( - f'You have too few arguments: (args, kwargs) is ({args}, {kwargs})' - ) - kwargs = dict({k: v for k, v in zip(fields, args)}, **kwargs) - if input_trans is not None: - kwargs = input_trans(**kwargs) - return str_format.format(**kwargs) - - set_signature_of_func(_mk, fields) - - if module is not None: - _mk.__module__ = module - - if name is not None: - _mk.__qualname__ = name - - return _mk -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_modules/sqlalchemy/sql/sqltypes.html b/docs/_modules/sqlalchemy/sql/sqltypes.html deleted file mode 100644 index 811f4f8..0000000 --- a/docs/_modules/sqlalchemy/sql/sqltypes.html +++ /dev/null @@ -1,3262 +0,0 @@ - - - - - - - - sqlalchemy.sql.sqltypes — py2store 0.0.7 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Source code for sqlalchemy.sql.sqltypes

-# sql/sqltypes.py
-# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
-# <see AUTHORS file>
-#
-# This module is part of SQLAlchemy and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-
-"""SQL specific types.
-
-"""
-
-import codecs
-import datetime as dt
-import decimal
-import json
-
-from . import elements
-from . import operators
-from . import type_api
-from .base import _bind_or_error
-from .base import NO_ARG
-from .base import SchemaEventTarget
-from .elements import _defer_name
-from .elements import _literal_as_binds
-from .elements import quoted_name
-from .elements import Slice
-from .elements import TypeCoerce as type_coerce  # noqa
-from .type_api import Emulated
-from .type_api import NativeForEmulated  # noqa
-from .type_api import to_instance
-from .type_api import TypeDecorator
-from .type_api import TypeEngine
-from .type_api import Variant
-from .. import event
-from .. import exc
-from .. import inspection
-from .. import processors
-from .. import util
-from ..util import compat
-from ..util import langhelpers
-from ..util import pickle
-
-
-if util.jython:
-    import array
-
-
-class _LookupExpressionAdapter(object):
-
-    """Mixin expression adaptations based on lookup tables.
-
-    These rules are currently used by the numeric, integer and date types
-    which have detailed cross-expression coercion rules.
-
-    """
-
-    @property
-    def _expression_adaptations(self):
-        raise NotImplementedError()
-
-    class Comparator(TypeEngine.Comparator):
-        _blank_dict = util.immutabledict()
-
-        def _adapt_expression(self, op, other_comparator):
-            othertype = other_comparator.type._type_affinity
-            lookup = self.type._expression_adaptations.get(
-                op, self._blank_dict
-            ).get(othertype, self.type)
-            if lookup is othertype:
-                return (op, other_comparator.type)
-            elif lookup is self.type._type_affinity:
-                return (op, self.type)
-            else:
-                return (op, to_instance(lookup))
-
-    comparator_factory = Comparator
-
-
-class Concatenable(object):
-
-    """A mixin that marks a type as supporting 'concatenation',
-    typically strings."""
-
-    class Comparator(TypeEngine.Comparator):
-        def _adapt_expression(self, op, other_comparator):
-            if op is operators.add and isinstance(
-                other_comparator,
-                (Concatenable.Comparator, NullType.Comparator),
-            ):
-                return operators.concat_op, self.expr.type
-            else:
-                return super(Concatenable.Comparator, self)._adapt_expression(
-                    op, other_comparator
-                )
-
-    comparator_factory = Comparator
-
-
-class Indexable(object):
-    """A mixin that marks a type as supporting indexing operations,
-    such as array or JSON structures.
-
-
-    .. versionadded:: 1.1.0
-
-
-    """
-
-    class Comparator(TypeEngine.Comparator):
-        def _setup_getitem(self, index):
-            raise NotImplementedError()
-
-        def __getitem__(self, index):
-            (
-                adjusted_op,
-                adjusted_right_expr,
-                result_type,
-            ) = self._setup_getitem(index)
-            return self.operate(
-                adjusted_op, adjusted_right_expr, result_type=result_type
-            )
-
-    comparator_factory = Comparator
-
-
-class String(Concatenable, TypeEngine):
-
-    """The base for all string and character types.
-
-    In SQL, corresponds to VARCHAR.  Can also take Python unicode objects
-    and encode to the database's encoding in bind params (and the reverse for
-    result sets.)
-
-    The `length` field is usually required when the `String` type is
-    used within a CREATE TABLE statement, as VARCHAR requires a length
-    on most databases.
-
-    """
-
-    __visit_name__ = "string"
-
-    @util.deprecated_params(
-        convert_unicode=(
-            "1.3",
-            "The :paramref:`.String.convert_unicode` parameter is deprecated "
-            "and will be removed in a future release.  All modern DBAPIs "
-            "now support Python Unicode directly and this parameter is "
-            "unnecessary.",
-        ),
-        unicode_error=(
-            "1.3",
-            "The :paramref:`.String.unicode_errors` parameter is deprecated "
-            "and will be removed in a future release.  This parameter is "
-            "unnecessary for modern Python DBAPIs and degrades performance "
-            "significantly.",
-        ),
-    )
-    def __init__(
-        self,
-        length=None,
-        collation=None,
-        convert_unicode=False,
-        unicode_error=None,
-        _warn_on_bytestring=False,
-        _expect_unicode=False,
-    ):
-        """
-        Create a string-holding type.
-
-        :param length: optional, a length for the column for use in
-          DDL and CAST expressions.  May be safely omitted if no ``CREATE
-          TABLE`` will be issued.  Certain databases may require a
-          ``length`` for use in DDL, and will raise an exception when
-          the ``CREATE TABLE`` DDL is issued if a ``VARCHAR``
-          with no length is included.  Whether the value is
-          interpreted as bytes or characters is database specific.
-
-        :param collation: Optional, a column-level collation for
-          use in DDL and CAST expressions.  Renders using the
-          COLLATE keyword supported by SQLite, MySQL, and PostgreSQL.
-          E.g.::
-
-            >>> from sqlalchemy import cast, select, String
-            >>> print(select([cast('some string', String(collation='utf8'))]))
-            SELECT CAST(:param_1 AS VARCHAR COLLATE utf8) AS anon_1
-
-        :param convert_unicode: When set to ``True``, the
-          :class:`.String` type will assume that
-          input is to be passed as Python Unicode objects under Python 2,
-          and results returned as Python Unicode objects.
-          In the rare circumstance that the DBAPI does not support
-          Python unicode under Python 2, SQLAlchemy will use its own
-          encoder/decoder functionality on strings, referring to the
-          value of the :paramref:`_sa.create_engine.encoding` parameter
-          parameter passed to :func:`_sa.create_engine` as the encoding.
-
-          For the extremely rare case that Python Unicode
-          is to be encoded/decoded by SQLAlchemy on a backend
-          that *does* natively support Python Unicode,
-          the string value ``"force"`` can be passed here which will
-          cause SQLAlchemy's encode/decode services to be
-          used unconditionally.
-
-          .. note::
-
-            SQLAlchemy's unicode-conversion flags and features only apply
-            to Python 2; in Python 3, all string objects are Unicode objects.
-            For this reason, as well as the fact that virtually all modern
-            DBAPIs now support Unicode natively even under Python 2,
-            the :paramref:`.String.convert_unicode` flag is inherently a
-            legacy feature.
-
-          .. note::
-
-            In the vast majority of cases, the :class:`.Unicode` or
-            :class:`.UnicodeText` datatypes should be used for a
-            :class:`_schema.Column` that expects to store non-ascii data.
-            These
-            datatypes will ensure that the correct types are used on the
-            database side as well as set up the correct Unicode behaviors
-            under Python 2.
-
-          .. seealso::
-
-            :paramref:`_sa.create_engine.convert_unicode` -
-            :class:`_engine.Engine`-wide parameter
-
-        :param unicode_error: Optional, a method to use to handle Unicode
-          conversion errors. Behaves like the ``errors`` keyword argument to
-          the standard library's ``string.decode()`` functions, requires
-          that :paramref:`.String.convert_unicode` is set to
-          ``"force"``
-
-        """
-        if unicode_error is not None and convert_unicode != "force":
-            raise exc.ArgumentError(
-                "convert_unicode must be 'force' " "when unicode_error is set."
-            )
-
-        self.length = length
-        self.collation = collation
-        self._expect_unicode = convert_unicode or _expect_unicode
-        self._expect_unicode_error = unicode_error
-
-        self._warn_on_bytestring = _warn_on_bytestring
-
-    def literal_processor(self, dialect):
-        def process(value):
-            value = value.replace("'", "''")
-
-            if dialect.identifier_preparer._double_percents:
-                value = value.replace("%", "%%")
-
-            return "'%s'" % value
-
-        return process
-
-    def bind_processor(self, dialect):
-        if self._expect_unicode or dialect.convert_unicode:
-            if (
-                dialect.supports_unicode_binds
-                and self._expect_unicode != "force"
-            ):
-                if self._warn_on_bytestring:
-
-                    def process(value):
-                        if isinstance(value, util.binary_type):
-                            util.warn_limited(
-                                "Unicode type received non-unicode "
-                                "bind param value %r.",
-                                (util.ellipses_string(value),),
-                            )
-                        return value
-
-                    return process
-                else:
-                    return None
-            else:
-                encoder = codecs.getencoder(dialect.encoding)
-                warn_on_bytestring = self._warn_on_bytestring
-
-                def process(value):
-                    if isinstance(value, util.text_type):
-                        return encoder(value, self._expect_unicode_error)[0]
-                    elif warn_on_bytestring and value is not None:
-                        util.warn_limited(
-                            "Unicode type received non-unicode bind "
-                            "param value %r.",
-                            (util.ellipses_string(value),),
-                        )
-                    return value
-
-            return process
-        else:
-            return None
-
-    def result_processor(self, dialect, coltype):
-        wants_unicode = self._expect_unicode or dialect.convert_unicode
-        needs_convert = wants_unicode and (
-            dialect.returns_unicode_strings is not True
-            or self._expect_unicode in ("force", "force_nocheck")
-        )
-        needs_isinstance = (
-            needs_convert
-            and dialect.returns_unicode_strings
-            and self._expect_unicode != "force_nocheck"
-        )
-        if needs_convert:
-            if needs_isinstance:
-                return processors.to_conditional_unicode_processor_factory(
-                    dialect.encoding, self._expect_unicode_error
-                )
-            else:
-                return processors.to_unicode_processor_factory(
-                    dialect.encoding, self._expect_unicode_error
-                )
-        else:
-            return None
-
-    @property
-    def python_type(self):
-        if self._expect_unicode:
-            return util.text_type
-        else:
-            return str
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.STRING
-
-    @classmethod
-    def _warn_deprecated_unicode(cls):
-        util.warn_deprecated(
-            "The convert_unicode on Engine and String as well as the "
-            "unicode_error flag on String are deprecated.  All modern "
-            "DBAPIs now support Python Unicode natively under Python 2, and "
-            "under Python 3 all strings are inherently Unicode.  These flags "
-            "will be removed in a future release."
-        )
-
-
-class Text(String):
-
-    """A variably sized string type.
-
-    In SQL, usually corresponds to CLOB or TEXT. Can also take Python
-    unicode objects and encode to the database's encoding in bind
-    params (and the reverse for result sets.)  In general, TEXT objects
-    do not have a length; while some databases will accept a length
-    argument here, it will be rejected by others.
-
-    """
-
-    __visit_name__ = "text"
-
-
-class Unicode(String):
-
-    """A variable length Unicode string type.
-
-    The :class:`.Unicode` type is a :class:`.String` subclass
-    that assumes input and output as Python ``unicode`` data,
-    and in that regard is equivalent to the usage of the
-    ``convert_unicode`` flag with the :class:`.String` type.
-    However, unlike plain :class:`.String`, it also implies an
-    underlying column type that is explicitly supporting of non-ASCII
-    data, such as ``NVARCHAR`` on Oracle and SQL Server.
-    This can impact the output of ``CREATE TABLE`` statements
-    and ``CAST`` functions at the dialect level, and can
-    also affect the handling of bound parameters in some
-    specific DBAPI scenarios.
-
-    The encoding used by the :class:`.Unicode` type is usually
-    determined by the DBAPI itself; most modern DBAPIs
-    feature support for Python ``unicode`` objects as bound
-    values and result set values, and the encoding should
-    be configured as detailed in the notes for the target
-    DBAPI in the :ref:`dialect_toplevel` section.
-
-    For those DBAPIs which do not support, or are not configured
-    to accommodate Python ``unicode`` objects
-    directly, SQLAlchemy does the encoding and decoding
-    outside of the DBAPI.   The encoding in this scenario
-    is determined by the ``encoding`` flag passed to
-    :func:`_sa.create_engine`.
-
-    When using the :class:`.Unicode` type, it is only appropriate
-    to pass Python ``unicode`` objects, and not plain ``str``.
-    If a plain ``str`` is passed under Python 2, a warning
-    is emitted.  If you notice your application emitting these warnings but
-    you're not sure of the source of them, the Python
-    ``warnings`` filter, documented at
-    http://docs.python.org/library/warnings.html,
-    can be used to turn these warnings into exceptions
-    which will illustrate a stack trace::
-
-      import warnings
-      warnings.simplefilter('error')
-
-    For an application that wishes to pass plain bytestrings
-    and Python ``unicode`` objects to the ``Unicode`` type
-    equally, the bytestrings must first be decoded into
-    unicode.  The recipe at :ref:`coerce_to_unicode` illustrates
-    how this is done.
-
-    .. seealso::
-
-        :class:`.UnicodeText` - unlengthed textual counterpart
-        to :class:`.Unicode`.
-
-    """
-
-    __visit_name__ = "unicode"
-
-    def __init__(self, length=None, **kwargs):
-        """
-        Create a :class:`.Unicode` object.
-
-        Parameters are the same as that of :class:`.String`,
-        with the exception that ``convert_unicode``
-        defaults to ``True``.
-
-        """
-        kwargs.setdefault("_expect_unicode", True)
-        kwargs.setdefault("_warn_on_bytestring", True)
-        super(Unicode, self).__init__(length=length, **kwargs)
-
-
-class UnicodeText(Text):
-
-    """An unbounded-length Unicode string type.
-
-    See :class:`.Unicode` for details on the unicode
-    behavior of this object.
-
-    Like :class:`.Unicode`, usage the :class:`.UnicodeText` type implies a
-    unicode-capable type being used on the backend, such as
-    ``NCLOB``, ``NTEXT``.
-
-    """
-
-    __visit_name__ = "unicode_text"
-
-    def __init__(self, length=None, **kwargs):
-        """
-        Create a Unicode-converting Text type.
-
-        Parameters are the same as that of :class:`_expression.TextClause`,
-        with the exception that ``convert_unicode``
-        defaults to ``True``.
-
-        """
-        kwargs.setdefault("_expect_unicode", True)
-        kwargs.setdefault("_warn_on_bytestring", True)
-        super(UnicodeText, self).__init__(length=length, **kwargs)
-
-    def _warn_deprecated_unicode(self):
-        pass
-
-
-class Integer(_LookupExpressionAdapter, TypeEngine):
-
-    """A type for ``int`` integers."""
-
-    __visit_name__ = "integer"
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.NUMBER
-
-    @property
-    def python_type(self):
-        return int
-
-    def literal_processor(self, dialect):
-        def process(value):
-            return str(value)
-
-        return process
-
-    @util.memoized_property
-    def _expression_adaptations(self):
-        # TODO: need a dictionary object that will
-        # handle operators generically here, this is incomplete
-        return {
-            operators.add: {
-                Date: Date,
-                Integer: self.__class__,
-                Numeric: Numeric,
-            },
-            operators.mul: {
-                Interval: Interval,
-                Integer: self.__class__,
-                Numeric: Numeric,
-            },
-            operators.div: {Integer: self.__class__, Numeric: Numeric},
-            operators.truediv: {Integer: self.__class__, Numeric: Numeric},
-            operators.sub: {Integer: self.__class__, Numeric: Numeric},
-        }
-
-
-class SmallInteger(Integer):
-
-    """A type for smaller ``int`` integers.
-
-    Typically generates a ``SMALLINT`` in DDL, and otherwise acts like
-    a normal :class:`.Integer` on the Python side.
-
-    """
-
-    __visit_name__ = "small_integer"
-
-
-class BigInteger(Integer):
-
-    """A type for bigger ``int`` integers.
-
-    Typically generates a ``BIGINT`` in DDL, and otherwise acts like
-    a normal :class:`.Integer` on the Python side.
-
-    """
-
-    __visit_name__ = "big_integer"
-
-
-class Numeric(_LookupExpressionAdapter, TypeEngine):
-
-    """A type for fixed precision numbers, such as ``NUMERIC`` or ``DECIMAL``.
-
-    This type returns Python ``decimal.Decimal`` objects by default, unless
-    the :paramref:`.Numeric.asdecimal` flag is set to False, in which case
-    they are coerced to Python ``float`` objects.
-
-    .. note::
-
-        The :class:`.Numeric` type is designed to receive data from a database
-        type that is explicitly known to be a decimal type
-        (e.g. ``DECIMAL``, ``NUMERIC``, others) and not a floating point
-        type (e.g. ``FLOAT``, ``REAL``, others).
-        If the database column on the server is in fact a floating-point
-        type, such as ``FLOAT`` or ``REAL``, use the :class:`.Float`
-        type or a subclass, otherwise numeric coercion between
-        ``float``/``Decimal`` may or may not function as expected.
-
-    .. note::
-
-       The Python ``decimal.Decimal`` class is generally slow
-       performing; cPython 3.3 has now switched to use the `cdecimal
-       <http://pypi.python.org/pypi/cdecimal/>`_ library natively. For
-       older Python versions, the ``cdecimal`` library can be patched
-       into any application where it will replace the ``decimal``
-       library fully, however this needs to be applied globally and
-       before any other modules have been imported, as follows::
-
-           import sys
-           import cdecimal
-           sys.modules["decimal"] = cdecimal
-
-       Note that the ``cdecimal`` and ``decimal`` libraries are **not
-       compatible with each other**, so patching ``cdecimal`` at the
-       global level is the only way it can be used effectively with
-       various DBAPIs that hardcode to import the ``decimal`` library.
-
-    """
-
-    __visit_name__ = "numeric"
-
-    _default_decimal_return_scale = 10
-
-    def __init__(
-        self,
-        precision=None,
-        scale=None,
-        decimal_return_scale=None,
-        asdecimal=True,
-    ):
-        """
-        Construct a Numeric.
-
-        :param precision: the numeric precision for use in DDL ``CREATE
-          TABLE``.
-
-        :param scale: the numeric scale for use in DDL ``CREATE TABLE``.
-
-        :param asdecimal: default True.  Return whether or not
-          values should be sent as Python Decimal objects, or
-          as floats.   Different DBAPIs send one or the other based on
-          datatypes - the Numeric type will ensure that return values
-          are one or the other across DBAPIs consistently.
-
-        :param decimal_return_scale: Default scale to use when converting
-         from floats to Python decimals.  Floating point values will typically
-         be much longer due to decimal inaccuracy, and most floating point
-         database types don't have a notion of "scale", so by default the
-         float type looks for the first ten decimal places when converting.
-         Specifying this value will override that length.  Types which
-         do include an explicit ".scale" value, such as the base
-         :class:`.Numeric` as well as the MySQL float types, will use the
-         value of ".scale" as the default for decimal_return_scale, if not
-         otherwise specified.
-
-         .. versionadded:: 0.9.0
-
-        When using the ``Numeric`` type, care should be taken to ensure
-        that the asdecimal setting is appropriate for the DBAPI in use -
-        when Numeric applies a conversion from Decimal->float or float->
-        Decimal, this conversion incurs an additional performance overhead
-        for all result columns received.
-
-        DBAPIs that return Decimal natively (e.g. psycopg2) will have
-        better accuracy and higher performance with a setting of ``True``,
-        as the native translation to Decimal reduces the amount of floating-
-        point issues at play, and the Numeric type itself doesn't need
-        to apply any further conversions.  However, another DBAPI which
-        returns floats natively *will* incur an additional conversion
-        overhead, and is still subject to floating point data loss - in
-        which case ``asdecimal=False`` will at least remove the extra
-        conversion overhead.
-
-        """
-        self.precision = precision
-        self.scale = scale
-        self.decimal_return_scale = decimal_return_scale
-        self.asdecimal = asdecimal
-
-    @property
-    def _effective_decimal_return_scale(self):
-        if self.decimal_return_scale is not None:
-            return self.decimal_return_scale
-        elif getattr(self, "scale", None) is not None:
-            return self.scale
-        else:
-            return self._default_decimal_return_scale
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.NUMBER
-
-    def literal_processor(self, dialect):
-        def process(value):
-            return str(value)
-
-        return process
-
-    @property
-    def python_type(self):
-        if self.asdecimal:
-            return decimal.Decimal
-        else:
-            return float
-
-    def bind_processor(self, dialect):
-        if dialect.supports_native_decimal:
-            return None
-        else:
-            return processors.to_float
-
-    def result_processor(self, dialect, coltype):
-        if self.asdecimal:
-            if dialect.supports_native_decimal:
-                # we're a "numeric", DBAPI will give us Decimal directly
-                return None
-            else:
-                util.warn(
-                    "Dialect %s+%s does *not* support Decimal "
-                    "objects natively, and SQLAlchemy must "
-                    "convert from floating point - rounding "
-                    "errors and other issues may occur. Please "
-                    "consider storing Decimal numbers as strings "
-                    "or integers on this platform for lossless "
-                    "storage." % (dialect.name, dialect.driver)
-                )
-
-                # we're a "numeric", DBAPI returns floats, convert.
-                return processors.to_decimal_processor_factory(
-                    decimal.Decimal,
-                    self.scale
-                    if self.scale is not None
-                    else self._default_decimal_return_scale,
-                )
-        else:
-            if dialect.supports_native_decimal:
-                return processors.to_float
-            else:
-                return None
-
-    @util.memoized_property
-    def _expression_adaptations(self):
-        return {
-            operators.mul: {
-                Interval: Interval,
-                Numeric: self.__class__,
-                Integer: self.__class__,
-            },
-            operators.div: {Numeric: self.__class__, Integer: self.__class__},
-            operators.truediv: {
-                Numeric: self.__class__,
-                Integer: self.__class__,
-            },
-            operators.add: {Numeric: self.__class__, Integer: self.__class__},
-            operators.sub: {Numeric: self.__class__, Integer: self.__class__},
-        }
-
-
-class Float(Numeric):
-
-    """Type representing floating point types, such as ``FLOAT`` or ``REAL``.
-
-    This type returns Python ``float`` objects by default, unless the
-    :paramref:`.Float.asdecimal` flag is set to True, in which case they
-    are coerced to ``decimal.Decimal`` objects.
-
-    .. note::
-
-        The :class:`.Float` type is designed to receive data from a database
-        type that is explicitly known to be a floating point type
-        (e.g. ``FLOAT``, ``REAL``, others)
-        and not a decimal type (e.g. ``DECIMAL``, ``NUMERIC``, others).
-        If the database column on the server is in fact a Numeric
-        type, such as ``DECIMAL`` or ``NUMERIC``, use the :class:`.Numeric`
-        type or a subclass, otherwise numeric coercion between
-        ``float``/``Decimal`` may or may not function as expected.
-
-    """
-
-    __visit_name__ = "float"
-
-    scale = None
-
-    def __init__(
-        self, precision=None, asdecimal=False, decimal_return_scale=None
-    ):
-        r"""
-        Construct a Float.
-
-        :param precision: the numeric precision for use in DDL ``CREATE
-           TABLE``.
-
-        :param asdecimal: the same flag as that of :class:`.Numeric`, but
-          defaults to ``False``.   Note that setting this flag to ``True``
-          results in floating point conversion.
-
-        :param decimal_return_scale: Default scale to use when converting
-         from floats to Python decimals.  Floating point values will typically
-         be much longer due to decimal inaccuracy, and most floating point
-         database types don't have a notion of "scale", so by default the
-         float type looks for the first ten decimal places when converting.
-         Specifying this value will override that length.  Note that the
-         MySQL float types, which do include "scale", will use "scale"
-         as the default for decimal_return_scale, if not otherwise specified.
-
-         .. versionadded:: 0.9.0
-
-        """
-        self.precision = precision
-        self.asdecimal = asdecimal
-        self.decimal_return_scale = decimal_return_scale
-
-    def result_processor(self, dialect, coltype):
-        if self.asdecimal:
-            return processors.to_decimal_processor_factory(
-                decimal.Decimal, self._effective_decimal_return_scale
-            )
-        elif dialect.supports_native_decimal:
-            return processors.to_float
-        else:
-            return None
-
-
-class DateTime(_LookupExpressionAdapter, TypeEngine):
-
-    """A type for ``datetime.datetime()`` objects.
-
-    Date and time types return objects from the Python ``datetime``
-    module.  Most DBAPIs have built in support for the datetime
-    module, with the noted exception of SQLite.  In the case of
-    SQLite, date and time types are stored as strings which are then
-    converted back to datetime objects when rows are returned.
-
-    For the time representation within the datetime type, some
-    backends include additional options, such as timezone support and
-    fractional seconds support.  For fractional seconds, use the
-    dialect-specific datatype, such as :class:`.mysql.TIME`.  For
-    timezone support, use at least the :class:`_types.TIMESTAMP` datatype,
-    if not the dialect-specific datatype object.
-
-    """
-
-    __visit_name__ = "datetime"
-
-    def __init__(self, timezone=False):
-        """Construct a new :class:`.DateTime`.
-
-        :param timezone: boolean.  Indicates that the datetime type should
-         enable timezone support, if available on the
-         **base date/time-holding type only**.   It is recommended
-         to make use of the :class:`_types.TIMESTAMP` datatype directly when
-         using this flag, as some databases include separate generic
-         date/time-holding types distinct from the timezone-capable
-         TIMESTAMP datatype, such as Oracle.
-
-
-        """
-        self.timezone = timezone
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.DATETIME
-
-    @property
-    def python_type(self):
-        return dt.datetime
-
-    @util.memoized_property
-    def _expression_adaptations(self):
-
-        # Based on http://www.postgresql.org/docs/current/\
-        # static/functions-datetime.html.
-
-        return {
-            operators.add: {Interval: self.__class__},
-            operators.sub: {Interval: self.__class__, DateTime: Interval},
-        }
-
-
-class Date(_LookupExpressionAdapter, TypeEngine):
-
-    """A type for ``datetime.date()`` objects."""
-
-    __visit_name__ = "date"
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.DATETIME
-
-    @property
-    def python_type(self):
-        return dt.date
-
-    @util.memoized_property
-    def _expression_adaptations(self):
-        # Based on http://www.postgresql.org/docs/current/\
-        # static/functions-datetime.html.
-
-        return {
-            operators.add: {
-                Integer: self.__class__,
-                Interval: DateTime,
-                Time: DateTime,
-            },
-            operators.sub: {
-                # date - integer = date
-                Integer: self.__class__,
-                # date - date = integer.
-                Date: Integer,
-                Interval: DateTime,
-                # date - datetime = interval,
-                # this one is not in the PG docs
-                # but works
-                DateTime: Interval,
-            },
-        }
-
-
-class Time(_LookupExpressionAdapter, TypeEngine):
-
-    """A type for ``datetime.time()`` objects."""
-
-    __visit_name__ = "time"
-
-    def __init__(self, timezone=False):
-        self.timezone = timezone
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.DATETIME
-
-    @property
-    def python_type(self):
-        return dt.time
-
-    @util.memoized_property
-    def _expression_adaptations(self):
-        # Based on http://www.postgresql.org/docs/current/\
-        # static/functions-datetime.html.
-
-        return {
-            operators.add: {Date: DateTime, Interval: self.__class__},
-            operators.sub: {Time: Interval, Interval: self.__class__},
-        }
-
-
-class _Binary(TypeEngine):
-
-    """Define base behavior for binary types."""
-
-    def __init__(self, length=None):
-        self.length = length
-
-    def literal_processor(self, dialect):
-        def process(value):
-            value = value.decode(dialect.encoding).replace("'", "''")
-            return "'%s'" % value
-
-        return process
-
-    @property
-    def python_type(self):
-        return util.binary_type
-
-    # Python 3 - sqlite3 doesn't need the `Binary` conversion
-    # here, though pg8000 does to indicate "bytea"
-    def bind_processor(self, dialect):
-        if dialect.dbapi is None:
-            return None
-
-        DBAPIBinary = dialect.dbapi.Binary
-
-        def process(value):
-            if value is not None:
-                return DBAPIBinary(value)
-            else:
-                return None
-
-        return process
-
-    # Python 3 has native bytes() type
-    # both sqlite3 and pg8000 seem to return it,
-    # psycopg2 as of 2.5 returns 'memoryview'
-    if util.py2k:
-
-        def result_processor(self, dialect, coltype):
-            if util.jython:
-
-                def process(value):
-                    if value is not None:
-                        if isinstance(value, array.array):
-                            return value.tostring()
-                        return str(value)
-                    else:
-                        return None
-
-            else:
-                process = processors.to_str
-            return process
-
-    else:
-
-        def result_processor(self, dialect, coltype):
-            def process(value):
-                if value is not None:
-                    value = bytes(value)
-                return value
-
-            return process
-
-    def coerce_compared_value(self, op, value):
-        """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
-
-        if isinstance(value, util.string_types):
-            return self
-        else:
-            return super(_Binary, self).coerce_compared_value(op, value)
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.BINARY
-
-
-class LargeBinary(_Binary):
-
-    """A type for large binary byte data.
-
-    The :class:`.LargeBinary` type corresponds to a large and/or unlengthed
-    binary type for the target platform, such as BLOB on MySQL and BYTEA for
-    PostgreSQL.  It also handles the necessary conversions for the DBAPI.
-
-    """
-
-    __visit_name__ = "large_binary"
-
-    def __init__(self, length=None):
-        """
-        Construct a LargeBinary type.
-
-        :param length: optional, a length for the column for use in
-          DDL statements, for those binary types that accept a length,
-          such as the MySQL BLOB type.
-
-        """
-        _Binary.__init__(self, length=length)
-
-
-@util.deprecated_cls(
-    "0.6",
-    "The :class:`.Binary` class is deprecated and will be removed "
-    "in a future relase.  Please use :class:`.LargeBinary`.",
-)
-class Binary(LargeBinary):
-    def __init__(self, *arg, **kw):
-        LargeBinary.__init__(self, *arg, **kw)
-
-
-class SchemaType(SchemaEventTarget):
-
-    """Mark a type as possibly requiring schema-level DDL for usage.
-
-    Supports types that must be explicitly created/dropped (i.e. PG ENUM type)
-    as well as types that are complimented by table or schema level
-    constraints, triggers, and other rules.
-
-    :class:`.SchemaType` classes can also be targets for the
-    :meth:`.DDLEvents.before_parent_attach` and
-    :meth:`.DDLEvents.after_parent_attach` events, where the events fire off
-    surrounding the association of the type object with a parent
-    :class:`_schema.Column`.
-
-    .. seealso::
-
-        :class:`.Enum`
-
-        :class:`.Boolean`
-
-
-    """
-
-    def __init__(
-        self,
-        name=None,
-        schema=None,
-        metadata=None,
-        inherit_schema=False,
-        quote=None,
-        _create_events=True,
-    ):
-        if name is not None:
-            self.name = quoted_name(name, quote)
-        else:
-            self.name = None
-        self.schema = schema
-        self.metadata = metadata
-        self.inherit_schema = inherit_schema
-        self._create_events = _create_events
-
-        if _create_events and self.metadata:
-            event.listen(
-                self.metadata,
-                "before_create",
-                util.portable_instancemethod(self._on_metadata_create),
-            )
-            event.listen(
-                self.metadata,
-                "after_drop",
-                util.portable_instancemethod(self._on_metadata_drop),
-            )
-
-    def _translate_schema(self, effective_schema, map_):
-        return map_.get(effective_schema, effective_schema)
-
-    def _set_parent(self, column):
-        column._on_table_attach(util.portable_instancemethod(self._set_table))
-
-    def _variant_mapping_for_set_table(self, column):
-        if isinstance(column.type, Variant):
-            variant_mapping = column.type.mapping.copy()
-            variant_mapping["_default"] = column.type.impl
-        else:
-            variant_mapping = None
-        return variant_mapping
-
-    def _set_table(self, column, table):
-        if self.inherit_schema:
-            self.schema = table.schema
-
-        if not self._create_events:
-            return
-
-        variant_mapping = self._variant_mapping_for_set_table(column)
-
-        event.listen(
-            table,
-            "before_create",
-            util.portable_instancemethod(
-                self._on_table_create, {"variant_mapping": variant_mapping}
-            ),
-        )
-        event.listen(
-            table,
-            "after_drop",
-            util.portable_instancemethod(
-                self._on_table_drop, {"variant_mapping": variant_mapping}
-            ),
-        )
-        if self.metadata is None:
-            # TODO: what's the difference between self.metadata
-            # and table.metadata here ?
-            event.listen(
-                table.metadata,
-                "before_create",
-                util.portable_instancemethod(
-                    self._on_metadata_create,
-                    {"variant_mapping": variant_mapping},
-                ),
-            )
-            event.listen(
-                table.metadata,
-                "after_drop",
-                util.portable_instancemethod(
-                    self._on_metadata_drop,
-                    {"variant_mapping": variant_mapping},
-                ),
-            )
-
-    def copy(self, **kw):
-        return self.adapt(self.__class__, _create_events=True)
-
-    def adapt(self, impltype, **kw):
-        schema = kw.pop("schema", self.schema)
-        metadata = kw.pop("metadata", self.metadata)
-        _create_events = kw.pop("_create_events", False)
-        return impltype(
-            name=self.name,
-            schema=schema,
-            inherit_schema=self.inherit_schema,
-            metadata=metadata,
-            _create_events=_create_events,
-            **kw
-        )
-
-    @property
-    def bind(self):
-        return self.metadata and self.metadata.bind or None
-
-    def create(self, bind=None, checkfirst=False):
-        """Issue CREATE DDL for this type, if applicable."""
-
-        if bind is None:
-            bind = _bind_or_error(self)
-        t = self.dialect_impl(bind.dialect)
-        if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
-            t.create(bind=bind, checkfirst=checkfirst)
-
-    def drop(self, bind=None, checkfirst=False):
-        """Issue DROP DDL for this type, if applicable."""
-
-        if bind is None:
-            bind = _bind_or_error(self)
-        t = self.dialect_impl(bind.dialect)
-        if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
-            t.drop(bind=bind, checkfirst=checkfirst)
-
-    def _on_table_create(self, target, bind, **kw):
-        if not self._is_impl_for_variant(bind.dialect, kw):
-            return
-
-        t = self.dialect_impl(bind.dialect)
-        if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
-            t._on_table_create(target, bind, **kw)
-
-    def _on_table_drop(self, target, bind, **kw):
-        if not self._is_impl_for_variant(bind.dialect, kw):
-            return
-
-        t = self.dialect_impl(bind.dialect)
-        if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
-            t._on_table_drop(target, bind, **kw)
-
-    def _on_metadata_create(self, target, bind, **kw):
-        if not self._is_impl_for_variant(bind.dialect, kw):
-            return
-
-        t = self.dialect_impl(bind.dialect)
-        if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
-            t._on_metadata_create(target, bind, **kw)
-
-    def _on_metadata_drop(self, target, bind, **kw):
-        if not self._is_impl_for_variant(bind.dialect, kw):
-            return
-
-        t = self.dialect_impl(bind.dialect)
-        if t.__class__ is not self.__class__ and isinstance(t, SchemaType):
-            t._on_metadata_drop(target, bind, **kw)
-
-    def _is_impl_for_variant(self, dialect, kw):
-        variant_mapping = kw.pop("variant_mapping", None)
-        if variant_mapping is None:
-            return True
-
-        if (
-            dialect.name in variant_mapping
-            and variant_mapping[dialect.name] is self
-        ):
-            return True
-        elif dialect.name not in variant_mapping:
-            return variant_mapping["_default"] is self
-
-
-class Enum(Emulated, String, SchemaType):
-    """Generic Enum Type.
-
-    The :class:`.Enum` type provides a set of possible string values
-    which the column is constrained towards.
-
-    The :class:`.Enum` type will make use of the backend's native "ENUM"
-    type if one is available; otherwise, it uses a VARCHAR datatype and
-    produces a CHECK constraint.  Use of the backend-native enum type
-    can be disabled using the :paramref:`.Enum.native_enum` flag, and
-    the production of the CHECK constraint is configurable using the
-    :paramref:`.Enum.create_constraint` flag.
-
-    The :class:`.Enum` type also provides in-Python validation of string
-    values during both read and write operations.  When reading a value
-    from the database in a result set, the string value is always checked
-    against the list of possible values and a ``LookupError`` is raised
-    if no match is found.  When passing a value to the database as a
-    plain string within a SQL statement, if the
-    :paramref:`.Enum.validate_strings` parameter is
-    set to True, a ``LookupError`` is raised for any string value that's
-    not located in the given list of possible values; note that this
-    impacts usage of LIKE expressions with enumerated values (an unusual
-    use case).
-
-    .. versionchanged:: 1.1 the :class:`.Enum` type now provides in-Python
-       validation of input values as well as on data being returned by
-       the database.
-
-    The source of enumerated values may be a list of string values, or
-    alternatively a PEP-435-compliant enumerated class.  For the purposes
-    of the :class:`.Enum` datatype, this class need only provide a
-    ``__members__`` method.
-
-    When using an enumerated class, the enumerated objects are used
-    both for input and output, rather than strings as is the case with
-    a plain-string enumerated type::
-
-        import enum
-        class MyEnum(enum.Enum):
-            one = 1
-            two = 2
-            three = 3
-
-        t = Table(
-            'data', MetaData(),
-            Column('value', Enum(MyEnum))
-        )
-
-        connection.execute(t.insert(), {"value": MyEnum.two})
-        assert connection.scalar(t.select()) is MyEnum.two
-
-    Above, the string names of each element, e.g. "one", "two", "three",
-    are persisted to the database; the values of the Python Enum, here
-    indicated as integers, are **not** used; the value of each enum can
-    therefore be any kind of Python object whether or not it is persistable.
-
-    In order to persist the values and not the names, the
-    :paramref:`.Enum.values_callable` parameter may be used.   The value of
-    this parameter is a user-supplied callable, which  is intended to be used
-    with a PEP-435-compliant enumerated class and  returns a list of string
-    values to be persisted.   For a simple enumeration that uses string values,
-    a callable such as  ``lambda x: [e.value for e in x]`` is sufficient.
-
-    .. versionadded:: 1.1 - support for PEP-435-style enumerated
-       classes.
-
-
-    .. seealso::
-
-        :class:`_postgresql.ENUM` - PostgreSQL-specific type,
-        which has additional functionality.
-
-        :class:`.mysql.ENUM` - MySQL-specific type
-
-    """
-
-    __visit_name__ = "enum"
-
-    @util.deprecated_params(
-        convert_unicode=(
-            "1.3",
-            "The :paramref:`.Enum.convert_unicode` parameter is deprecated "
-            "and will be removed in a future release.  All modern DBAPIs "
-            "now support Python Unicode directly and this parameter is "
-            "unnecessary.",
-        )
-    )
-    def __init__(self, *enums, **kw):
-        r"""Construct an enum.
-
-        Keyword arguments which don't apply to a specific backend are ignored
-        by that backend.
-
-        :param \*enums: either exactly one PEP-435 compliant enumerated type
-           or one or more string or unicode enumeration labels. If unicode
-           labels are present, the `convert_unicode` flag is auto-enabled.
-
-           .. versionadded:: 1.1 a PEP-435 style enumerated class may be
-              passed.
-
-        :param convert_unicode: Enable unicode-aware bind parameter and
-           result-set processing for this Enum's data. This is set
-           automatically based on the presence of unicode label strings.
-
-        :param create_constraint: defaults to True.  When creating a non-native
-           enumerated type, also build a CHECK constraint on the database
-           against the valid values.
-
-           .. versionadded:: 1.1 - added :paramref:`.Enum.create_constraint`
-              which provides the option to disable the production of the
-              CHECK constraint for a non-native enumerated type.
-
-        :param metadata: Associate this type directly with a ``MetaData``
-           object. For types that exist on the target database as an
-           independent schema construct (PostgreSQL), this type will be
-           created and dropped within ``create_all()`` and ``drop_all()``
-           operations. If the type is not associated with any ``MetaData``
-           object, it will associate itself with each ``Table`` in which it is
-           used, and will be created when any of those individual tables are
-           created, after a check is performed for its existence. The type is
-           only dropped when ``drop_all()`` is called for that ``Table``
-           object's metadata, however.
-
-        :param name: The name of this type. This is required for PostgreSQL
-           and any future supported database which requires an explicitly
-           named type, or an explicitly named constraint in order to generate
-           the type and/or a table that uses it. If a PEP-435 enumerated
-           class was used, its name (converted to lower case) is used by
-           default.
-
-        :param native_enum: Use the database's native ENUM type when
-           available. Defaults to True. When False, uses VARCHAR + check
-           constraint for all backends. The VARCHAR length can be controlled
-           with :paramref:`.Enum.length`
-
-        :param length: Allows specifying a custom length for the VARCHAR
-           when :paramref:`.Enum.native_enum` is False. By default it uses the
-           length of the longest value.
-
-           .. versionadded:: 1.3.16
-
-        :param schema: Schema name of this type. For types that exist on the
-           target database as an independent schema construct (PostgreSQL),
-           this parameter specifies the named schema in which the type is
-           present.
-
-           .. note::
-
-                The ``schema`` of the :class:`.Enum` type does not
-                by default make use of the ``schema`` established on the
-                owning :class:`_schema.Table`.  If this behavior is desired,
-                set the ``inherit_schema`` flag to ``True``.
-
-        :param quote: Set explicit quoting preferences for the type's name.
-
-        :param inherit_schema: When ``True``, the "schema" from the owning
-           :class:`_schema.Table`
-           will be copied to the "schema" attribute of this
-           :class:`.Enum`, replacing whatever value was passed for the
-           ``schema`` attribute.   This also takes effect when using the
-           :meth:`_schema.Table.tometadata` operation.
-
-        :param validate_strings: when True, string values that are being
-           passed to the database in a SQL statement will be checked
-           for validity against the list of enumerated values.  Unrecognized
-           values will result in a ``LookupError`` being raised.
-
-           .. versionadded:: 1.1.0b2
-
-        :param values_callable: A callable which will be passed the PEP-435
-           compliant enumerated type, which should then return a list of string
-           values to be persisted. This allows for alternate usages such as
-           using the string value of an enum to be persisted to the database
-           instead of its name.
-
-           .. versionadded:: 1.2.3
-
-        :param sort_key_function: a Python callable which may be used as the
-           "key" argument in the Python ``sorted()`` built-in.   The SQLAlchemy
-           ORM requires that primary key columns which are mapped must
-           be sortable in some way.  When using an unsortable enumeration
-           object such as a Python 3 ``Enum`` object, this parameter may be
-           used to set a default sort key function for the objects.  By
-           default, the database value of the enumeration is used as the
-           sorting function.
-
-           .. versionadded:: 1.3.8
-
-
-
-        """
-        self._enum_init(enums, kw)
-
-    @property
-    def _enums_argument(self):
-        if self.enum_class is not None:
-            return [self.enum_class]
-        else:
-            return self.enums
-
-    def _enum_init(self, enums, kw):
-        """internal init for :class:`.Enum` and subclasses.
-
-        friendly init helper used by subclasses to remove
-        all the Enum-specific keyword arguments from kw.  Allows all
-        other arguments in kw to pass through.
-
-        """
-        self.native_enum = kw.pop("native_enum", True)
-        self.create_constraint = kw.pop("create_constraint", True)
-        self.values_callable = kw.pop("values_callable", None)
-        self._sort_key_function = kw.pop("sort_key_function", NO_ARG)
-        length_arg = kw.pop("length", NO_ARG)
-
-        values, objects = self._parse_into_values(enums, kw)
-        self._setup_for_values(values, objects, kw)
-
-        convert_unicode = kw.pop("convert_unicode", None)
-        self.validate_strings = kw.pop("validate_strings", False)
-
-        if convert_unicode is None:
-            for e in self.enums:
-                # this is all py2k logic that can go away for py3k only,
-                # "expect unicode" will always be implicitly true
-                if isinstance(e, util.text_type):
-                    _expect_unicode = True
-                    break
-            else:
-                _expect_unicode = False
-        else:
-            _expect_unicode = convert_unicode
-
-        if self.enums:
-            length = max(len(x) for x in self.enums)
-        else:
-            length = 0
-        if not self.native_enum and length_arg is not NO_ARG:
-            if length_arg < length:
-                raise ValueError(
-                    "When provided, length must be larger or equal"
-                    " than the length of the longest enum value. %s < %s"
-                    % (length_arg, length)
-                )
-            length = length_arg
-
-        self._valid_lookup[None] = self._object_lookup[None] = None
-
-        super(Enum, self).__init__(
-            length=length, _expect_unicode=_expect_unicode
-        )
-
-        if self.enum_class:
-            kw.setdefault("name", self.enum_class.__name__.lower())
-        SchemaType.__init__(
-            self,
-            name=kw.pop("name", None),
-            schema=kw.pop("schema", None),
-            metadata=kw.pop("metadata", None),
-            inherit_schema=kw.pop("inherit_schema", False),
-            quote=kw.pop("quote", None),
-            _create_events=kw.pop("_create_events", True),
-        )
-
-    def _parse_into_values(self, enums, kw):
-        if not enums and "_enums" in kw:
-            enums = kw.pop("_enums")
-
-        if len(enums) == 1 and hasattr(enums[0], "__members__"):
-            self.enum_class = enums[0]
-            members = self.enum_class.__members__
-            if self.values_callable:
-                values = self.values_callable(self.enum_class)
-            else:
-                values = list(members)
-            objects = [members[k] for k in members]
-            return values, objects
-        else:
-            self.enum_class = None
-            return enums, enums
-
-    def _setup_for_values(self, values, objects, kw):
-        self.enums = list(values)
-
-        self._valid_lookup = dict(zip(reversed(objects), reversed(values)))
-
-        self._object_lookup = dict(zip(values, objects))
-
-        self._valid_lookup.update(
-            [
-                (value, self._valid_lookup[self._object_lookup[value]])
-                for value in values
-            ]
-        )
-
-    @property
-    def sort_key_function(self):
-        if self._sort_key_function is NO_ARG:
-            return self._db_value_for_elem
-        else:
-            return self._sort_key_function
-
-    @property
-    def native(self):
-        return self.native_enum
-
-    def _db_value_for_elem(self, elem):
-        try:
-            return self._valid_lookup[elem]
-        except KeyError as err:
-            # for unknown string values, we return as is.  While we can
-            # validate these if we wanted, that does not allow for lesser-used
-            # end-user use cases, such as using a LIKE comparison with an enum,
-            # or for an application that wishes to apply string tests to an
-            # ENUM (see [ticket:3725]).  While we can decide to differentiate
-            # here between an INSERT statement and a criteria used in a SELECT,
-            # for now we're staying conservative w/ behavioral changes (perhaps
-            # someone has a trigger that handles strings on INSERT)
-            if not self.validate_strings and isinstance(
-                elem, compat.string_types
-            ):
-                return elem
-            else:
-                util.raise_(
-                    LookupError(
-                        "'%s' is not among the defined enum values. "
-                        "Enum name: %s. Possible values: %s"
-                        % (
-                            elem,
-                            self.name,
-                            langhelpers.repr_tuple_names(self.enums),
-                        )
-                    ),
-                    replace_context=err,
-                )
-
-    class Comparator(String.Comparator):
-        def _adapt_expression(self, op, other_comparator):
-            op, typ = super(Enum.Comparator, self)._adapt_expression(
-                op, other_comparator
-            )
-            if op is operators.concat_op:
-                typ = String(
-                    self.type.length, _expect_unicode=self.type._expect_unicode
-                )
-            return op, typ
-
-    comparator_factory = Comparator
-
-    def _object_value_for_elem(self, elem):
-        try:
-            return self._object_lookup[elem]
-        except KeyError as err:
-            util.raise_(
-                LookupError(
-                    "'%s' is not among the defined enum values. "
-                    "Enum name: %s. Possible values: %s"
-                    % (
-                        elem,
-                        self.name,
-                        langhelpers.repr_tuple_names(self.enums),
-                    )
-                ),
-                replace_context=err,
-            )
-
-    def __repr__(self):
-        return util.generic_repr(
-            self,
-            additional_kw=[("native_enum", True)],
-            to_inspect=[Enum, SchemaType],
-        )
-
-    def adapt_to_emulated(self, impltype, **kw):
-        kw.setdefault("_expect_unicode", self._expect_unicode)
-        kw.setdefault("validate_strings", self.validate_strings)
-        kw.setdefault("name", self.name)
-        kw.setdefault("schema", self.schema)
-        kw.setdefault("inherit_schema", self.inherit_schema)
-        kw.setdefault("metadata", self.metadata)
-        kw.setdefault("_create_events", False)
-        kw.setdefault("native_enum", self.native_enum)
-        kw.setdefault("values_callable", self.values_callable)
-        kw.setdefault("create_constraint", self.create_constraint)
-        kw.setdefault("length", self.length)
-        assert "_enums" in kw
-        return impltype(**kw)
-
-    def adapt(self, impltype, **kw):
-        kw["_enums"] = self._enums_argument
-        return super(Enum, self).adapt(impltype, **kw)
-
-    def _should_create_constraint(self, compiler, **kw):
-        if not self._is_impl_for_variant(compiler.dialect, kw):
-            return False
-        return (
-            not self.native_enum or not compiler.dialect.supports_native_enum
-        )
-
-    @util.dependencies("sqlalchemy.sql.schema")
-    def _set_table(self, schema, column, table):
-        SchemaType._set_table(self, column, table)
-
-        if not self.create_constraint:
-            return
-
-        variant_mapping = self._variant_mapping_for_set_table(column)
-
-        e = schema.CheckConstraint(
-            type_coerce(column, self).in_(self.enums),
-            name=_defer_name(self.name),
-            _create_rule=util.portable_instancemethod(
-                self._should_create_constraint,
-                {"variant_mapping": variant_mapping},
-            ),
-            _type_bound=True,
-        )
-        assert e.table is table
-
-    def literal_processor(self, dialect):
-        parent_processor = super(Enum, self).literal_processor(dialect)
-
-        def process(value):
-            value = self._db_value_for_elem(value)
-            if parent_processor:
-                value = parent_processor(value)
-            return value
-
-        return process
-
-    def bind_processor(self, dialect):
-        def process(value):
-            value = self._db_value_for_elem(value)
-            if parent_processor:
-                value = parent_processor(value)
-            return value
-
-        parent_processor = super(Enum, self).bind_processor(dialect)
-        return process
-
-    def result_processor(self, dialect, coltype):
-        parent_processor = super(Enum, self).result_processor(dialect, coltype)
-
-        def process(value):
-            if parent_processor:
-                value = parent_processor(value)
-
-            value = self._object_value_for_elem(value)
-            return value
-
-        return process
-
-    def copy(self, **kw):
-        return SchemaType.copy(self, **kw)
-
-    @property
-    def python_type(self):
-        if self.enum_class:
-            return self.enum_class
-        else:
-            return super(Enum, self).python_type
-
-
-class PickleType(TypeDecorator):
-    """Holds Python objects, which are serialized using pickle.
-
-    PickleType builds upon the Binary type to apply Python's
-    ``pickle.dumps()`` to incoming objects, and ``pickle.loads()`` on
-    the way out, allowing any pickleable Python object to be stored as
-    a serialized binary field.
-
-    To allow ORM change events to propagate for elements associated
-    with :class:`.PickleType`, see :ref:`mutable_toplevel`.
-
-    """
-
-    impl = LargeBinary
-
-    def __init__(
-        self, protocol=pickle.HIGHEST_PROTOCOL, pickler=None, comparator=None
-    ):
-        """
-        Construct a PickleType.
-
-        :param protocol: defaults to ``pickle.HIGHEST_PROTOCOL``.
-
-        :param pickler: defaults to cPickle.pickle or pickle.pickle if
-          cPickle is not available.  May be any object with
-          pickle-compatible ``dumps`` and ``loads`` methods.
-
-        :param comparator: a 2-arg callable predicate used
-          to compare values of this type.  If left as ``None``,
-          the Python "equals" operator is used to compare values.
-
-        """
-        self.protocol = protocol
-        self.pickler = pickler or pickle
-        self.comparator = comparator
-        super(PickleType, self).__init__()
-
-    def __reduce__(self):
-        return PickleType, (self.protocol, None, self.comparator)
-
-    def bind_processor(self, dialect):
-        impl_processor = self.impl.bind_processor(dialect)
-        dumps = self.pickler.dumps
-        protocol = self.protocol
-        if impl_processor:
-
-            def process(value):
-                if value is not None:
-                    value = dumps(value, protocol)
-                return impl_processor(value)
-
-        else:
-
-            def process(value):
-                if value is not None:
-                    value = dumps(value, protocol)
-                return value
-
-        return process
-
-    def result_processor(self, dialect, coltype):
-        impl_processor = self.impl.result_processor(dialect, coltype)
-        loads = self.pickler.loads
-        if impl_processor:
-
-            def process(value):
-                value = impl_processor(value)
-                if value is None:
-                    return None
-                return loads(value)
-
-        else:
-
-            def process(value):
-                if value is None:
-                    return None
-                return loads(value)
-
-        return process
-
-    def compare_values(self, x, y):
-        if self.comparator:
-            return self.comparator(x, y)
-        else:
-            return x == y
-
-
-class Boolean(Emulated, TypeEngine, SchemaType):
-
-    """A bool datatype.
-
-    :class:`.Boolean` typically uses BOOLEAN or SMALLINT on the DDL side,
-    and on the Python side deals in ``True`` or ``False``.
-
-    The :class:`.Boolean` datatype currently has two levels of assertion
-    that the values persisted are simple true/false values.  For all
-    backends, only the Python values ``None``, ``True``, ``False``, ``1``
-    or ``0`` are accepted as parameter values.   For those backends that
-    don't support a "native boolean" datatype, a CHECK constraint is also
-    created on the target column.   Production of the CHECK constraint
-    can be disabled by passing the :paramref:`.Boolean.create_constraint`
-    flag set to ``False``.
-
-    .. versionchanged:: 1.2 the :class:`.Boolean` datatype now asserts that
-       incoming Python values are already in pure boolean form.
-
-
-    """
-
-    __visit_name__ = "boolean"
-    native = True
-
-    def __init__(self, create_constraint=True, name=None, _create_events=True):
-        """Construct a Boolean.
-
-        :param create_constraint: defaults to True.  If the boolean
-          is generated as an int/smallint, also create a CHECK constraint
-          on the table that ensures 1 or 0 as a value.
-
-        :param name: if a CHECK constraint is generated, specify
-          the name of the constraint.
-
-        """
-        self.create_constraint = create_constraint
-        self.name = name
-        self._create_events = _create_events
-
-    def _should_create_constraint(self, compiler, **kw):
-        if not self._is_impl_for_variant(compiler.dialect, kw):
-            return False
-        return (
-            not compiler.dialect.supports_native_boolean
-            and compiler.dialect.non_native_boolean_check_constraint
-        )
-
-    @util.dependencies("sqlalchemy.sql.schema")
-    def _set_table(self, schema, column, table):
-        if not self.create_constraint:
-            return
-
-        variant_mapping = self._variant_mapping_for_set_table(column)
-
-        e = schema.CheckConstraint(
-            type_coerce(column, self).in_([0, 1]),
-            name=_defer_name(self.name),
-            _create_rule=util.portable_instancemethod(
-                self._should_create_constraint,
-                {"variant_mapping": variant_mapping},
-            ),
-            _type_bound=True,
-        )
-        assert e.table is table
-
-    @property
-    def python_type(self):
-        return bool
-
-    _strict_bools = frozenset([None, True, False])
-
-    def _strict_as_bool(self, value):
-        if value not in self._strict_bools:
-            if not isinstance(value, int):
-                raise TypeError("Not a boolean value: %r" % value)
-            else:
-                raise ValueError(
-                    "Value %r is not None, True, or False" % value
-                )
-        return value
-
-    def literal_processor(self, dialect):
-        compiler = dialect.statement_compiler(dialect, None)
-        true = compiler.visit_true(None)
-        false = compiler.visit_false(None)
-
-        def process(value):
-            return true if self._strict_as_bool(value) else false
-
-        return process
-
-    def bind_processor(self, dialect):
-        _strict_as_bool = self._strict_as_bool
-        if dialect.supports_native_boolean:
-            _coerce = bool
-        else:
-            _coerce = int
-
-        def process(value):
-            value = _strict_as_bool(value)
-            if value is not None:
-                value = _coerce(value)
-            return value
-
-        return process
-
-    def result_processor(self, dialect, coltype):
-        if dialect.supports_native_boolean:
-            return None
-        else:
-            return processors.int_to_boolean
-
-
-class _AbstractInterval(_LookupExpressionAdapter, TypeEngine):
-    @util.memoized_property
-    def _expression_adaptations(self):
-        # Based on http://www.postgresql.org/docs/current/\
-        # static/functions-datetime.html.
-
-        return {
-            operators.add: {
-                Date: DateTime,
-                Interval: self.__class__,
-                DateTime: DateTime,
-                Time: Time,
-            },
-            operators.sub: {Interval: self.__class__},
-            operators.mul: {Numeric: self.__class__},
-            operators.truediv: {Numeric: self.__class__},
-            operators.div: {Numeric: self.__class__},
-        }
-
-    @property
-    def _type_affinity(self):
-        return Interval
-
-    def coerce_compared_value(self, op, value):
-        """See :meth:`.TypeEngine.coerce_compared_value` for a description."""
-        return self.impl.coerce_compared_value(op, value)
-
-
-class Interval(Emulated, _AbstractInterval, TypeDecorator):
-
-    """A type for ``datetime.timedelta()`` objects.
-
-    The Interval type deals with ``datetime.timedelta`` objects.  In
-    PostgreSQL, the native ``INTERVAL`` type is used; for others, the
-    value is stored as a date which is relative to the "epoch"
-    (Jan. 1, 1970).
-
-    Note that the ``Interval`` type does not currently provide date arithmetic
-    operations on platforms which do not support interval types natively. Such
-    operations usually require transformation of both sides of the expression
-    (such as, conversion of both sides into integer epoch values first) which
-    currently is a manual procedure (such as via
-    :attr:`~sqlalchemy.sql.expression.func`).
-
-    """
-
-    impl = DateTime
-    epoch = dt.datetime.utcfromtimestamp(0)
-
-    def __init__(self, native=True, second_precision=None, day_precision=None):
-        """Construct an Interval object.
-
-        :param native: when True, use the actual
-          INTERVAL type provided by the database, if
-          supported (currently PostgreSQL, Oracle).
-          Otherwise, represent the interval data as
-          an epoch value regardless.
-
-        :param second_precision: For native interval types
-          which support a "fractional seconds precision" parameter,
-          i.e. Oracle and PostgreSQL
-
-        :param day_precision: for native interval types which
-          support a "day precision" parameter, i.e. Oracle.
-
-        """
-        super(Interval, self).__init__()
-        self.native = native
-        self.second_precision = second_precision
-        self.day_precision = day_precision
-
-    @property
-    def python_type(self):
-        return dt.timedelta
-
-    def adapt_to_emulated(self, impltype, **kw):
-        return _AbstractInterval.adapt(self, impltype, **kw)
-
-    def bind_processor(self, dialect):
-        impl_processor = self.impl.bind_processor(dialect)
-        epoch = self.epoch
-        if impl_processor:
-
-            def process(value):
-                if value is not None:
-                    value = epoch + value
-                return impl_processor(value)
-
-        else:
-
-            def process(value):
-                if value is not None:
-                    value = epoch + value
-                return value
-
-        return process
-
-    def result_processor(self, dialect, coltype):
-        impl_processor = self.impl.result_processor(dialect, coltype)
-        epoch = self.epoch
-        if impl_processor:
-
-            def process(value):
-                value = impl_processor(value)
-                if value is None:
-                    return None
-                return value - epoch
-
-        else:
-
-            def process(value):
-                if value is None:
-                    return None
-                return value - epoch
-
-        return process
-
-
-class JSON(Indexable, TypeEngine):
-    """Represent a SQL JSON type.
-
-    .. note::  :class:`_types.JSON`
-       is provided as a facade for vendor-specific
-       JSON types.  Since it supports JSON SQL operations, it only
-       works on backends that have an actual JSON type, currently:
-
-       * PostgreSQL
-
-       * MySQL as of version 5.7 (MariaDB as of the 10.2 series does not)
-
-       * SQLite as of version 3.9
-
-    :class:`_types.JSON` is part of the Core in support of the growing
-    popularity of native JSON datatypes.
-
-    The :class:`_types.JSON` type stores arbitrary JSON format data, e.g.::
-
-        data_table = Table('data_table', metadata,
-            Column('id', Integer, primary_key=True),
-            Column('data', JSON)
-        )
-
-        with engine.connect() as conn:
-            conn.execute(
-                data_table.insert(),
-                data = {"key1": "value1", "key2": "value2"}
-            )
-
-    **JSON-Specific Expression Operators**
-
-    The :class:`_types.JSON`
-    datatype provides these additional SQL operations:
-
-    * Keyed index operations::
-
-        data_table.c.data['some key']
-
-    * Integer index operations::
-
-        data_table.c.data[3]
-
-    * Path index operations::
-
-        data_table.c.data[('key_1', 'key_2', 5, ..., 'key_n')]
-
-    * Data casters for specific JSON element types, subsequent to an index
-      or path operation being invoked::
-
-        data_table.c.data["some key"].as_integer()
-
-      .. versionadded:: 1.3.11
-
-    Additional operations may be available from the dialect-specific versions
-    of :class:`_types.JSON`, such as :class:`_postgresql.JSON` and
-    :class:`_postgresql.JSONB` which both offer additional PostgreSQL-specific
-    operations.
-
-    **Casting JSON Elements to Other Types**
-
-    Index operations, i.e. those invoked by calling upon the expression using
-    the Python bracket operator as in ``some_column['some key']``, return an
-    expression object whose type defaults to :class:`_types.JSON` by default,
-    so that
-    further JSON-oriented instructions may be called upon the result type.
-    However, it is likely more common that an index operation is expected
-    to return a specific scalar element, such as a string or integer.  In
-    order to provide access to these elements in a backend-agnostic way,
-    a series of data casters are provided:
-
-    * :meth:`.JSON.Comparator.as_string` - return the element as a string
-
-    * :meth:`.JSON.Comparator.as_boolean` - return the element as a boolean
-
-    * :meth:`.JSON.Comparator.as_float` - return the element as a float
-
-    * :meth:`.JSON.Comparator.as_integer` - return the element as an integer
-
-    These data casters are implemented by supporting dialects in order to
-    assure that comparisons to the above types will work as expected, such as::
-
-        # integer comparison
-        data_table.c.data["some_integer_key"].as_integer() == 5
-
-        # boolean comparison
-        data_table.c.data["some_boolean"].as_boolean() == True
-
-    .. versionadded:: 1.3.11 Added type-specific casters for the basic JSON
-       data element types.
-
-    .. note::
-
-        The data caster functions are new in version 1.3.11, and supersede
-        the previous documented approaches of using CAST; for reference,
-        this looked like::
-
-           from sqlalchemy import cast, type_coerce
-           from sqlalchemy import String, JSON
-           cast(
-               data_table.c.data['some_key'], String
-           ) == type_coerce(55, JSON)
-
-        The above case now works directly as::
-
-            data_table.c.data['some_key'].as_integer() == 5
-
-        For details on the previous comparison approach within the 1.3.x
-        series, see the documentation for SQLAlchemy 1.2 or the included HTML
-        files in the doc/ directory of the version's distribution.
-
-    **Detecting Changes in JSON columns when using the ORM**
-
-    The :class:`_types.JSON` type, when used with the SQLAlchemy ORM, does not
-    detect in-place mutations to the structure.  In order to detect these, the
-    :mod:`sqlalchemy.ext.mutable` extension must be used.  This extension will
-    allow "in-place" changes to the datastructure to produce events which
-    will be detected by the unit of work.  See the example at :class:`.HSTORE`
-    for a simple example involving a dictionary.
-
-    **Support for JSON null vs. SQL NULL**
-
-    When working with NULL values, the :class:`_types.JSON`
-    type recommends the
-    use of two specific constants in order to differentiate between a column
-    that evaluates to SQL NULL, e.g. no value, vs. the JSON-encoded string
-    of ``"null"``.   To insert or select against a value that is SQL NULL,
-    use the constant :func:`.null`::
-
-        from sqlalchemy import null
-        conn.execute(table.insert(), json_value=null())
-
-    To insert or select against a value that is JSON ``"null"``, use the
-    constant :attr:`_types.JSON.NULL`::
-
-        conn.execute(table.insert(), json_value=JSON.NULL)
-
-    The :class:`_types.JSON` type supports a flag
-    :paramref:`_types.JSON.none_as_null` which when set to True will result
-    in the Python constant ``None`` evaluating to the value of SQL
-    NULL, and when set to False results in the Python constant
-    ``None`` evaluating to the value of JSON ``"null"``.    The Python
-    value ``None`` may be used in conjunction with either
-    :attr:`_types.JSON.NULL` and :func:`.null` in order to indicate NULL
-    values, but care must be taken as to the value of the
-    :paramref:`_types.JSON.none_as_null` in these cases.
-
-    **Customizing the JSON Serializer**
-
-    The JSON serializer and deserializer used by :class:`_types.JSON`
-    defaults to
-    Python's ``json.dumps`` and ``json.loads`` functions; in the case of the
-    psycopg2 dialect, psycopg2 may be using its own custom loader function.
-
-    In order to affect the serializer / deserializer, they are currently
-    configurable at the :func:`_sa.create_engine` level via the
-    :paramref:`_sa.create_engine.json_serializer` and
-    :paramref:`_sa.create_engine.json_deserializer` parameters.  For example,
-    to turn off ``ensure_ascii``::
-
-        engine = create_engine(
-            "sqlite://",
-            json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False))
-
-    .. versionchanged:: 1.3.7
-
-        SQLite dialect's ``json_serializer`` and ``json_deserializer``
-        parameters renamed from ``_json_serializer`` and
-        ``_json_deserializer``.
-
-    .. seealso::
-
-        :class:`_postgresql.JSON`
-
-        :class:`_postgresql.JSONB`
-
-        :class:`.mysql.JSON`
-
-        :class:`_sqlite.JSON`
-
-    .. versionadded:: 1.1
-
-
-    """
-
-    __visit_name__ = "JSON"
-
-    hashable = False
-    NULL = util.symbol("JSON_NULL")
-    """Describe the json value of NULL.
-
-    This value is used to force the JSON value of ``"null"`` to be
-    used as the value.   A value of Python ``None`` will be recognized
-    either as SQL NULL or JSON ``"null"``, based on the setting
-    of the :paramref:`_types.JSON.none_as_null` flag; the
-    :attr:`_types.JSON.NULL`
-    constant can be used to always resolve to JSON ``"null"`` regardless
-    of this setting.  This is in contrast to the :func:`_expression.null`
-    construct,
-    which always resolves to SQL NULL.  E.g.::
-
-        from sqlalchemy import null
-        from sqlalchemy.dialects.postgresql import JSON
-
-        # will *always* insert SQL NULL
-        obj1 = MyObject(json_value=null())
-
-        # will *always* insert JSON string "null"
-        obj2 = MyObject(json_value=JSON.NULL)
-
-        session.add_all([obj1, obj2])
-        session.commit()
-
-    In order to set JSON NULL as a default value for a column, the most
-    transparent method is to use :func:`_expression.text`::
-
-        Table(
-            'my_table', metadata,
-            Column('json_data', JSON, default=text("'null'"))
-        )
-
-    While it is possible to use :attr:`_types.JSON.NULL` in this context, the
-    :attr:`_types.JSON.NULL` value will be returned as the value of the
-    column,
-    which in the context of the ORM or other repurposing of the default
-    value, may not be desirable.  Using a SQL expression means the value
-    will be re-fetched from the database within the context of retrieving
-    generated defaults.
-
-
-    """
-
-    def __init__(self, none_as_null=False):
-        """Construct a :class:`_types.JSON` type.
-
-        :param none_as_null=False: if True, persist the value ``None`` as a
-         SQL NULL value, not the JSON encoding of ``null``.   Note that
-         when this flag is False, the :func:`.null` construct can still
-         be used to persist a NULL value::
-
-             from sqlalchemy import null
-             conn.execute(table.insert(), data=null())
-
-         .. note::
-
-              :paramref:`_types.JSON.none_as_null` does **not** apply to the
-              values passed to :paramref:`_schema.Column.default` and
-              :paramref:`_schema.Column.server_default`; a value of ``None``
-              passed for these parameters means "no default present".
-
-         .. seealso::
-
-              :attr:`.types.JSON.NULL`
-
-        """
-        self.none_as_null = none_as_null
-
-    class JSONElementType(TypeEngine):
-        """Common function for index / path elements in a JSON expression."""
-
-        _integer = Integer()
-        _string = String()
-
-        def string_bind_processor(self, dialect):
-            return self._string._cached_bind_processor(dialect)
-
-        def string_literal_processor(self, dialect):
-            return self._string._cached_literal_processor(dialect)
-
-        def bind_processor(self, dialect):
-            int_processor = self._integer._cached_bind_processor(dialect)
-            string_processor = self.string_bind_processor(dialect)
-
-            def process(value):
-                if int_processor and isinstance(value, int):
-                    value = int_processor(value)
-                elif string_processor and isinstance(value, util.string_types):
-                    value = string_processor(value)
-                return value
-
-            return process
-
-        def literal_processor(self, dialect):
-            int_processor = self._integer._cached_literal_processor(dialect)
-            string_processor = self.string_literal_processor(dialect)
-
-            def process(value):
-                if int_processor and isinstance(value, int):
-                    value = int_processor(value)
-                elif string_processor and isinstance(value, util.string_types):
-                    value = string_processor(value)
-                return value
-
-            return process
-
-    class JSONIndexType(JSONElementType):
-        """Placeholder for the datatype of a JSON index value.
-
-        This allows execution-time processing of JSON index values
-        for special syntaxes.
-
-        """
-
-    class JSONPathType(JSONElementType):
-        """Placeholder type for JSON path operations.
-
-        This allows execution-time processing of a path-based
-        index value into a specific SQL syntax.
-
-        """
-
-    class Comparator(Indexable.Comparator, Concatenable.Comparator):
-        """Define comparison operations for :class:`_types.JSON`."""
-
-        @util.dependencies("sqlalchemy.sql.default_comparator")
-        def _setup_getitem(self, default_comparator, index):
-            if not isinstance(index, util.string_types) and isinstance(
-                index, compat.collections_abc.Sequence
-            ):
-                index = default_comparator._check_literal(
-                    self.expr,
-                    operators.json_path_getitem_op,
-                    index,
-                    bindparam_type=JSON.JSONPathType,
-                )
-
-                operator = operators.json_path_getitem_op
-            else:
-                index = default_comparator._check_literal(
-                    self.expr,
-                    operators.json_getitem_op,
-                    index,
-                    bindparam_type=JSON.JSONIndexType,
-                )
-                operator = operators.json_getitem_op
-
-            return operator, index, self.type
-
-        def as_boolean(self):
-            """Cast an indexed value as boolean.
-
-            e.g.::
-
-                stmt = select([
-                    mytable.c.json_column['some_data'].as_boolean()
-                ]).where(
-                    mytable.c.json_column['some_data'].as_boolean() == True
-                )
-
-            .. versionadded:: 1.3.11
-
-            """
-            return self._binary_w_type(Boolean(), "as_boolean")
-
-        def as_string(self):
-            """Cast an indexed value as string.
-
-            e.g.::
-
-                stmt = select([
-                    mytable.c.json_column['some_data'].as_string()
-                ]).where(
-                    mytable.c.json_column['some_data'].as_string() ==
-                    'some string'
-                )
-
-            .. versionadded:: 1.3.11
-
-            """
-            return self._binary_w_type(String(), "as_string")
-
-        def as_integer(self):
-            """Cast an indexed value as integer.
-
-            e.g.::
-
-                stmt = select([
-                    mytable.c.json_column['some_data'].as_integer()
-                ]).where(
-                    mytable.c.json_column['some_data'].as_integer() == 5
-                )
-
-            .. versionadded:: 1.3.11
-
-            """
-            return self._binary_w_type(Integer(), "as_integer")
-
-        def as_float(self):
-            """Cast an indexed value as float.
-
-            e.g.::
-
-                stmt = select([
-                    mytable.c.json_column['some_data'].as_float()
-                ]).where(
-                    mytable.c.json_column['some_data'].as_float() == 29.75
-                )
-
-            .. versionadded:: 1.3.11
-
-            """
-            # note there's no Numeric or Decimal support here yet
-            return self._binary_w_type(Float(), "as_float")
-
-        def as_json(self):
-            """Cast an indexed value as JSON.
-
-            This is the default behavior of indexed elements in any case.
-
-            Note that comparison of full JSON structures may not be
-            supported by all backends.
-
-            .. versionadded:: 1.3.11
-
-            """
-            return self.expr
-
-        def _binary_w_type(self, typ, method_name):
-            if not isinstance(
-                self.expr, elements.BinaryExpression
-            ) or self.expr.operator not in (
-                operators.json_getitem_op,
-                operators.json_path_getitem_op,
-            ):
-                raise exc.InvalidRequestError(
-                    "The JSON cast operator JSON.%s() only works with a JSON "
-                    "index expression e.g. col['q'].%s()"
-                    % (method_name, method_name)
-                )
-            expr = self.expr._clone()
-            expr.type = typ
-            return expr
-
-    comparator_factory = Comparator
-
-    @property
-    def python_type(self):
-        return dict
-
-    @property
-    def should_evaluate_none(self):
-        """Alias of :attr:`_types.JSON.none_as_null`"""
-        return not self.none_as_null
-
-    @should_evaluate_none.setter
-    def should_evaluate_none(self, value):
-        self.none_as_null = not value
-
-    @util.memoized_property
-    def _str_impl(self):
-        return String(_expect_unicode=True)
-
-    def bind_processor(self, dialect):
-        string_process = self._str_impl.bind_processor(dialect)
-
-        json_serializer = dialect._json_serializer or json.dumps
-
-        def process(value):
-            if value is self.NULL:
-                value = None
-            elif isinstance(value, elements.Null) or (
-                value is None and self.none_as_null
-            ):
-                return None
-
-            serialized = json_serializer(value)
-            if string_process:
-                serialized = string_process(serialized)
-            return serialized
-
-        return process
-
-    def result_processor(self, dialect, coltype):
-        string_process = self._str_impl.result_processor(dialect, coltype)
-        json_deserializer = dialect._json_deserializer or json.loads
-
-        def process(value):
-            if value is None:
-                return None
-            if string_process:
-                value = string_process(value)
-            return json_deserializer(value)
-
-        return process
-
-
-class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine):
-    """Represent a SQL Array type.
-
-    .. note::  This type serves as the basis for all ARRAY operations.
-       However, currently **only the PostgreSQL backend has support
-       for SQL arrays in SQLAlchemy**.  It is recommended to use the
-       :class:`_postgresql.ARRAY` type directly when using ARRAY types
-       with PostgreSQL, as it provides additional operators specific
-       to that backend.
-
-    :class:`_types.ARRAY` is part of the Core in support of various SQL
-    standard functions such as :class:`_functions.array_agg`
-    which explicitly involve
-    arrays; however, with the exception of the PostgreSQL backend and possibly
-    some third-party dialects, no other SQLAlchemy built-in dialect has support
-    for this type.
-
-    An :class:`_types.ARRAY` type is constructed given the "type"
-    of element::
-
-        mytable = Table("mytable", metadata,
-                Column("data", ARRAY(Integer))
-            )
-
-    The above type represents an N-dimensional array,
-    meaning a supporting backend such as PostgreSQL will interpret values
-    with any number of dimensions automatically.   To produce an INSERT
-    construct that passes in a 1-dimensional array of integers::
-
-        connection.execute(
-                mytable.insert(),
-                data=[1,2,3]
-        )
-
-    The :class:`_types.ARRAY` type can be constructed given a fixed number
-    of dimensions::
-
-        mytable = Table("mytable", metadata,
-                Column("data", ARRAY(Integer, dimensions=2))
-            )
-
-    Sending a number of dimensions is optional, but recommended if the
-    datatype is to represent arrays of more than one dimension.  This number
-    is used:
-
-    * When emitting the type declaration itself to the database, e.g.
-      ``INTEGER[][]``
-
-    * When translating Python values to database values, and vice versa, e.g.
-      an ARRAY of :class:`.Unicode` objects uses this number to efficiently
-      access the string values inside of array structures without resorting
-      to per-row type inspection
-
-    * When used with the Python ``getitem`` accessor, the number of dimensions
-      serves to define the kind of type that the ``[]`` operator should
-      return, e.g. for an ARRAY of INTEGER with two dimensions::
-
-          >>> expr = table.c.column[5]  # returns ARRAY(Integer, dimensions=1)
-          >>> expr = expr[6]  # returns Integer
-
-    For 1-dimensional arrays, an :class:`_types.ARRAY` instance with no
-    dimension parameter will generally assume single-dimensional behaviors.
-
-    SQL expressions of type :class:`_types.ARRAY` have support for "index" and
-    "slice" behavior.  The Python ``[]`` operator works normally here, given
-    integer indexes or slices.  Arrays default to 1-based indexing.
-    The operator produces binary expression
-    constructs which will produce the appropriate SQL, both for
-    SELECT statements::
-
-        select([mytable.c.data[5], mytable.c.data[2:7]])
-
-    as well as UPDATE statements when the :meth:`_expression.Update.values`
-    method
-    is used::
-
-        mytable.update().values({
-            mytable.c.data[5]: 7,
-            mytable.c.data[2:7]: [1, 2, 3]
-        })
-
-    The :class:`_types.ARRAY` type also provides for the operators
-    :meth:`.types.ARRAY.Comparator.any` and
-    :meth:`.types.ARRAY.Comparator.all`. The PostgreSQL-specific version of
-    :class:`_types.ARRAY` also provides additional operators.
-
-    .. versionadded:: 1.1.0
-
-    .. seealso::
-
-        :class:`_postgresql.ARRAY`
-
-    """
-
-    __visit_name__ = "ARRAY"
-
-    zero_indexes = False
-    """If True, Python zero-based indexes should be interpreted as one-based
-    on the SQL expression side."""
-
-    class Comparator(Indexable.Comparator, Concatenable.Comparator):
-
-        """Define comparison operations for :class:`_types.ARRAY`.
-
-        More operators are available on the dialect-specific form
-        of this type.  See :class:`.postgresql.ARRAY.Comparator`.
-
-        """
-
-        def _setup_getitem(self, index):
-            if isinstance(index, slice):
-                return_type = self.type
-                if self.type.zero_indexes:
-                    index = slice(index.start + 1, index.stop + 1, index.step)
-                index = Slice(
-                    _literal_as_binds(
-                        index.start,
-                        name=self.expr.key,
-                        type_=type_api.INTEGERTYPE,
-                    ),
-                    _literal_as_binds(
-                        index.stop,
-                        name=self.expr.key,
-                        type_=type_api.INTEGERTYPE,
-                    ),
-                    _literal_as_binds(
-                        index.step,
-                        name=self.expr.key,
-                        type_=type_api.INTEGERTYPE,
-                    ),
-                )
-            else:
-                if self.type.zero_indexes:
-                    index += 1
-                if self.type.dimensions is None or self.type.dimensions == 1:
-                    return_type = self.type.item_type
-                else:
-                    adapt_kw = {"dimensions": self.type.dimensions - 1}
-                    return_type = self.type.adapt(
-                        self.type.__class__, **adapt_kw
-                    )
-
-            return operators.getitem, index, return_type
-
-        def contains(self, *arg, **kw):
-            raise NotImplementedError(
-                "ARRAY.contains() not implemented for the base "
-                "ARRAY type; please use the dialect-specific ARRAY type"
-            )
-
-        @util.dependencies("sqlalchemy.sql.elements")
-        def any(self, elements, other, operator=None):
-            """Return ``other operator ANY (array)`` clause.
-
-            Argument places are switched, because ANY requires array
-            expression to be on the right hand-side.
-
-            E.g.::
-
-                from sqlalchemy.sql import operators
-
-                conn.execute(
-                    select([table.c.data]).where(
-                            table.c.data.any(7, operator=operators.lt)
-                        )
-                )
-
-            :param other: expression to be compared
-            :param operator: an operator object from the
-             :mod:`sqlalchemy.sql.operators`
-             package, defaults to :func:`.operators.eq`.
-
-            .. seealso::
-
-                :func:`_expression.any_`
-
-                :meth:`.types.ARRAY.Comparator.all`
-
-            """
-            operator = operator if operator else operators.eq
-
-            # send plain BinaryExpression so that negate remains at None,
-            # leading to NOT expr for negation.
-            return elements.BinaryExpression(
-                elements._literal_as_binds(other),
-                elements.CollectionAggregate._create_any(self.expr),
-                operator,
-            )
-
-        @util.dependencies("sqlalchemy.sql.elements")
-        def all(self, elements, other, operator=None):
-            """Return ``other operator ALL (array)`` clause.
-
-            Argument places are switched, because ALL requires array
-            expression to be on the right hand-side.
-
-            E.g.::
-
-                from sqlalchemy.sql import operators
-
-                conn.execute(
-                    select([table.c.data]).where(
-                            table.c.data.all(7, operator=operators.lt)
-                        )
-                )
-
-            :param other: expression to be compared
-            :param operator: an operator object from the
-             :mod:`sqlalchemy.sql.operators`
-             package, defaults to :func:`.operators.eq`.
-
-            .. seealso::
-
-                :func:`_expression.all_`
-
-                :meth:`.types.ARRAY.Comparator.any`
-
-            """
-            operator = operator if operator else operators.eq
-
-            # send plain BinaryExpression so that negate remains at None,
-            # leading to NOT expr for negation.
-            return elements.BinaryExpression(
-                elements._literal_as_binds(other),
-                elements.CollectionAggregate._create_all(self.expr),
-                operator,
-            )
-
-    comparator_factory = Comparator
-
-    def __init__(
-        self, item_type, as_tuple=False, dimensions=None, zero_indexes=False
-    ):
-        """Construct an :class:`_types.ARRAY`.
-
-        E.g.::
-
-          Column('myarray', ARRAY(Integer))
-
-        Arguments are:
-
-        :param item_type: The data type of items of this array. Note that
-          dimensionality is irrelevant here, so multi-dimensional arrays like
-          ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as
-          ``ARRAY(ARRAY(Integer))`` or such.
-
-        :param as_tuple=False: Specify whether return results
-          should be converted to tuples from lists.  This parameter is
-          not generally needed as a Python list corresponds well
-          to a SQL array.
-
-        :param dimensions: if non-None, the ARRAY will assume a fixed
-         number of dimensions.   This impacts how the array is declared
-         on the database, how it goes about interpreting Python and
-         result values, as well as how expression behavior in conjunction
-         with the "getitem" operator works.  See the description at
-         :class:`_types.ARRAY` for additional detail.
-
-        :param zero_indexes=False: when True, index values will be converted
-         between Python zero-based and SQL one-based indexes, e.g.
-         a value of one will be added to all index values before passing
-         to the database.
-
-        """
-        if isinstance(item_type, ARRAY):
-            raise ValueError(
-                "Do not nest ARRAY types; ARRAY(basetype) "
-                "handles multi-dimensional arrays of basetype"
-            )
-        if isinstance(item_type, type):
-            item_type = item_type()
-        self.item_type = item_type
-        self.as_tuple = as_tuple
-        self.dimensions = dimensions
-        self.zero_indexes = zero_indexes
-
-    @property
-    def hashable(self):
-        return self.as_tuple
-
-    @property
-    def python_type(self):
-        return list
-
-    def compare_values(self, x, y):
-        return x == y
-
-    def _set_parent(self, column):
-        """Support SchemaEventTarget"""
-
-        if isinstance(self.item_type, SchemaEventTarget):
-            self.item_type._set_parent(column)
-
-    def _set_parent_with_dispatch(self, parent):
-        """Support SchemaEventTarget"""
-
-        super(ARRAY, self)._set_parent_with_dispatch(parent)
-
-        if isinstance(self.item_type, SchemaEventTarget):
-            self.item_type._set_parent_with_dispatch(parent)
-
-
-class REAL(Float):
-
-    """The SQL REAL type."""
-
-    __visit_name__ = "REAL"
-
-
-class FLOAT(Float):
-
-    """The SQL FLOAT type."""
-
-    __visit_name__ = "FLOAT"
-
-
-class NUMERIC(Numeric):
-
-    """The SQL NUMERIC type."""
-
-    __visit_name__ = "NUMERIC"
-
-
-class DECIMAL(Numeric):
-
-    """The SQL DECIMAL type."""
-
-    __visit_name__ = "DECIMAL"
-
-
-class INTEGER(Integer):
-
-    """The SQL INT or INTEGER type."""
-
-    __visit_name__ = "INTEGER"
-
-
-INT = INTEGER
-
-
-class SMALLINT(SmallInteger):
-
-    """The SQL SMALLINT type."""
-
-    __visit_name__ = "SMALLINT"
-
-
-class BIGINT(BigInteger):
-
-    """The SQL BIGINT type."""
-
-    __visit_name__ = "BIGINT"
-
-
-class TIMESTAMP(DateTime):
-
-    """The SQL TIMESTAMP type.
-
-    :class:`_types.TIMESTAMP` datatypes have support for timezone
-    storage on some backends, such as PostgreSQL and Oracle.  Use the
-    :paramref:`~types.TIMESTAMP.timezone` argument in order to enable
-    "TIMESTAMP WITH TIMEZONE" for these backends.
-
-    """
-
-    __visit_name__ = "TIMESTAMP"
-
-    def __init__(self, timezone=False):
-        """Construct a new :class:`_types.TIMESTAMP`.
-
-        :param timezone: boolean.  Indicates that the TIMESTAMP type should
-         enable timezone support, if available on the target database.
-         On a per-dialect basis is similar to "TIMESTAMP WITH TIMEZONE".
-         If the target database does not support timezones, this flag is
-         ignored.
-
-
-        """
-        super(TIMESTAMP, self).__init__(timezone=timezone)
-
-    def get_dbapi_type(self, dbapi):
-        return dbapi.TIMESTAMP
-
-
-class DATETIME(DateTime):
-
-    """The SQL DATETIME type."""
-
-    __visit_name__ = "DATETIME"
-
-
-class DATE(Date):
-
-    """The SQL DATE type."""
-
-    __visit_name__ = "DATE"
-
-
-class TIME(Time):
-
-    """The SQL TIME type."""
-
-    __visit_name__ = "TIME"
-
-
-class TEXT(Text):
-
-    """The SQL TEXT type."""
-
-    __visit_name__ = "TEXT"
-
-
-class CLOB(Text):
-
-    """The CLOB type.
-
-    This type is found in Oracle and Informix.
-    """
-
-    __visit_name__ = "CLOB"
-
-
-class VARCHAR(String):
-
-    """The SQL VARCHAR type."""
-
-    __visit_name__ = "VARCHAR"
-
-
-class NVARCHAR(Unicode):
-
-    """The SQL NVARCHAR type."""
-
-    __visit_name__ = "NVARCHAR"
-
-
-class CHAR(String):
-
-    """The SQL CHAR type."""
-
-    __visit_name__ = "CHAR"
-
-
-class NCHAR(Unicode):
-
-    """The SQL NCHAR type."""
-
-    __visit_name__ = "NCHAR"
-
-
-class BLOB(LargeBinary):
-
-    """The SQL BLOB type."""
-
-    __visit_name__ = "BLOB"
-
-
-class BINARY(_Binary):
-
-    """The SQL BINARY type."""
-
-    __visit_name__ = "BINARY"
-
-
-class VARBINARY(_Binary):
-
-    """The SQL VARBINARY type."""
-
-    __visit_name__ = "VARBINARY"
-
-
-class BOOLEAN(Boolean):
-
-    """The SQL BOOLEAN type."""
-
-    __visit_name__ = "BOOLEAN"
-
-
-class NullType(TypeEngine):
-
-    """An unknown type.
-
-    :class:`.NullType` is used as a default type for those cases where
-    a type cannot be determined, including:
-
-    * During table reflection, when the type of a column is not recognized
-      by the :class:`.Dialect`
-    * When constructing SQL expressions using plain Python objects of
-      unknown types (e.g. ``somecolumn == my_special_object``)
-    * When a new :class:`_schema.Column` is created,
-      and the given type is passed
-      as ``None`` or is not passed at all.
-
-    The :class:`.NullType` can be used within SQL expression invocation
-    without issue, it just has no behavior either at the expression
-    construction level or at the bind-parameter/result processing level.
-    :class:`.NullType` will result in a :exc:`.CompileError` if the compiler
-    is asked to render the type itself, such as if it is used in a
-    :func:`.cast` operation or within a schema creation operation such as that
-    invoked by :meth:`_schema.MetaData.create_all` or the
-    :class:`.CreateTable`
-    construct.
-
-    """
-
-    __visit_name__ = "null"
-
-    _isnull = True
-
-    hashable = False
-
-    def literal_processor(self, dialect):
-        def process(value):
-            return "NULL"
-
-        return process
-
-    class Comparator(TypeEngine.Comparator):
-        def _adapt_expression(self, op, other_comparator):
-            if isinstance(
-                other_comparator, NullType.Comparator
-            ) or not operators.is_commutative(op):
-                return op, self.expr.type
-            else:
-                return other_comparator._adapt_expression(op, self)
-
-    comparator_factory = Comparator
-
-
-class MatchType(Boolean):
-    """Refers to the return type of the MATCH operator.
-
-    As the :meth:`.ColumnOperators.match` is probably the most open-ended
-    operator in generic SQLAlchemy Core, we can't assume the return type
-    at SQL evaluation time, as MySQL returns a floating point, not a boolean,
-    and other backends might do something different.    So this type
-    acts as a placeholder, currently subclassing :class:`.Boolean`.
-    The type allows dialects to inject result-processing functionality
-    if needed, and on MySQL will return floating-point values.
-
-    .. versionadded:: 1.0.0
-
-    """
-
-
-NULLTYPE = NullType()
-BOOLEANTYPE = Boolean()
-STRINGTYPE = String()
-INTEGERTYPE = Integer()
-MATCHTYPE = MatchType()
-
-_type_map = {
-    int: Integer(),
-    float: Float(),
-    bool: BOOLEANTYPE,
-    decimal.Decimal: Numeric(),
-    dt.date: Date(),
-    dt.datetime: DateTime(),
-    dt.time: Time(),
-    dt.timedelta: Interval(),
-    util.NoneType: NULLTYPE,
-}
-
-if util.py3k:
-    _type_map[bytes] = LargeBinary()  # noqa
-    _type_map[str] = Unicode()
-else:
-    _type_map[unicode] = Unicode()  # noqa
-    _type_map[str] = String()
-
-_type_map_get = _type_map.get
-
-
-def _resolve_value_to_type(value):
-    _result_type = _type_map_get(type(value), False)
-    if _result_type is False:
-        # use inspect() to detect SQLAlchemy built-in
-        # objects.
-        insp = inspection.inspect(value, False)
-        if (
-            insp is not None
-            and
-            # foil mock.Mock() and other impostors by ensuring
-            # the inspection target itself self-inspects
-            insp.__class__ in inspection._registrars
-        ):
-            raise exc.ArgumentError(
-                "Object %r is not legal as a SQL literal value" % value
-            )
-        return NULLTYPE
-    else:
-        return _result_type
-
-
-# back-assign to type_api
-type_api.BOOLEANTYPE = BOOLEANTYPE
-type_api.STRINGTYPE = STRINGTYPE
-type_api.INTEGERTYPE = INTEGERTYPE
-type_api.NULLTYPE = NULLTYPE
-type_api.MATCHTYPE = MATCHTYPE
-type_api.INDEXABLE = Indexable
-type_api._resolve_value_to_type = _resolve_value_to_type
-TypeEngine.Comparator.BOOLEANTYPE = BOOLEANTYPE
-
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/_sources/http_docs.rst.txt b/docs/_sources/http_docs.rst.txt deleted file mode 100644 index b1a8444..0000000 --- a/docs/_sources/http_docs.rst.txt +++ /dev/null @@ -1 +0,0 @@ -.. automodule:: mockmodule.http_docs diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt deleted file mode 100644 index 6d94e72..0000000 --- a/docs/_sources/index.rst.txt +++ /dev/null @@ -1,13 +0,0 @@ -Welcome to py2store's documentation! -==================================== - - -.. include:: ./table_of_contents.rst - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/_sources/mockobjects.rst.txt b/docs/_sources/mockobjects.rst.txt deleted file mode 100644 index f710a7f..0000000 --- a/docs/_sources/mockobjects.rst.txt +++ /dev/null @@ -1,5 +0,0 @@ -Mock Objects -============ - -.. automodule:: mockmodule.mockobjects - :members: diff --git a/docs/_sources/module_docs/py2store.rst.txt b/docs/_sources/module_docs/py2store.rst.txt deleted file mode 100644 index 8801d26..0000000 --- a/docs/_sources/module_docs/py2store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store -======== -.. automodule:: py2store - :members: diff --git a/docs/_sources/module_docs/py2store/access.rst.txt b/docs/_sources/module_docs/py2store/access.rst.txt deleted file mode 100644 index ab53ffa..0000000 --- a/docs/_sources/module_docs/py2store/access.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.access -=============== -.. automodule:: py2store.access - :members: diff --git a/docs/_sources/module_docs/py2store/appendable.rst.txt b/docs/_sources/module_docs/py2store/appendable.rst.txt deleted file mode 100644 index 450c7eb..0000000 --- a/docs/_sources/module_docs/py2store/appendable.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.appendable -=================== -.. automodule:: py2store.appendable - :members: diff --git a/docs/_sources/module_docs/py2store/base.rst.txt b/docs/_sources/module_docs/py2store/base.rst.txt deleted file mode 100644 index a3a5892..0000000 --- a/docs/_sources/module_docs/py2store/base.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.base -============= -.. automodule:: py2store.base - :members: diff --git a/docs/_sources/module_docs/py2store/caching.rst.txt b/docs/_sources/module_docs/py2store/caching.rst.txt deleted file mode 100644 index b455a58..0000000 --- a/docs/_sources/module_docs/py2store/caching.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.caching -================ -.. automodule:: py2store.caching - :members: diff --git a/docs/_sources/module_docs/py2store/core.rst.txt b/docs/_sources/module_docs/py2store/core.rst.txt deleted file mode 100644 index 9a54cb1..0000000 --- a/docs/_sources/module_docs/py2store/core.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.core -============= -.. automodule:: py2store.core - :members: diff --git a/docs/_sources/module_docs/py2store/dig.rst.txt b/docs/_sources/module_docs/py2store/dig.rst.txt deleted file mode 100644 index d084d5c..0000000 --- a/docs/_sources/module_docs/py2store/dig.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.dig -============ -.. automodule:: py2store.dig - :members: diff --git a/docs/_sources/module_docs/py2store/errors.rst.txt b/docs/_sources/module_docs/py2store/errors.rst.txt deleted file mode 100644 index 917b152..0000000 --- a/docs/_sources/module_docs/py2store/errors.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.errors -=============== -.. automodule:: py2store.errors - :members: diff --git a/docs/_sources/module_docs/py2store/examples.rst.txt b/docs/_sources/module_docs/py2store/examples.rst.txt deleted file mode 100644 index 1af20ef..0000000 --- a/docs/_sources/module_docs/py2store/examples.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples -================= -.. automodule:: py2store.examples - :members: diff --git a/docs/_sources/module_docs/py2store/examples/code_navig.rst.txt b/docs/_sources/module_docs/py2store/examples/code_navig.rst.txt deleted file mode 100644 index 53797e8..0000000 --- a/docs/_sources/module_docs/py2store/examples/code_navig.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.code_navig -============================ -.. automodule:: py2store.examples.code_navig - :members: diff --git a/docs/_sources/module_docs/py2store/examples/dropbox_w_urllib.rst.txt b/docs/_sources/module_docs/py2store/examples/dropbox_w_urllib.rst.txt deleted file mode 100644 index 835398b..0000000 --- a/docs/_sources/module_docs/py2store/examples/dropbox_w_urllib.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.dropbox_w_urllib -================================== -.. automodule:: py2store.examples.dropbox_w_urllib - :members: diff --git a/docs/_sources/module_docs/py2store/examples/kv_walking.rst.txt b/docs/_sources/module_docs/py2store/examples/kv_walking.rst.txt deleted file mode 100644 index abdc655..0000000 --- a/docs/_sources/module_docs/py2store/examples/kv_walking.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.kv_walking -============================ -.. automodule:: py2store.examples.kv_walking - :members: diff --git a/docs/_sources/module_docs/py2store/examples/last_key_inserted.rst.txt b/docs/_sources/module_docs/py2store/examples/last_key_inserted.rst.txt deleted file mode 100644 index d1fa8d7..0000000 --- a/docs/_sources/module_docs/py2store/examples/last_key_inserted.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.last_key_inserted -=================================== -.. automodule:: py2store.examples.last_key_inserted - :members: diff --git a/docs/_sources/module_docs/py2store/examples/python_code_stats.rst.txt b/docs/_sources/module_docs/py2store/examples/python_code_stats.rst.txt deleted file mode 100644 index 6e362f7..0000000 --- a/docs/_sources/module_docs/py2store/examples/python_code_stats.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.python_code_stats -=================================== -.. automodule:: py2store.examples.python_code_stats - :members: diff --git a/docs/_sources/module_docs/py2store/examples/write_caches.rst.txt b/docs/_sources/module_docs/py2store/examples/write_caches.rst.txt deleted file mode 100644 index b149e11..0000000 --- a/docs/_sources/module_docs/py2store/examples/write_caches.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.write_caches -============================== -.. automodule:: py2store.examples.write_caches - :members: diff --git a/docs/_sources/module_docs/py2store/ext.rst.txt b/docs/_sources/module_docs/py2store/ext.rst.txt deleted file mode 100644 index 0e2ae09..0000000 --- a/docs/_sources/module_docs/py2store/ext.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext -============ -.. automodule:: py2store.ext - :members: diff --git a/docs/_sources/module_docs/py2store/ext/audio.rst.txt b/docs/_sources/module_docs/py2store/ext/audio.rst.txt deleted file mode 100644 index a4c8a4e..0000000 --- a/docs/_sources/module_docs/py2store/ext/audio.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.audio -================== -.. automodule:: py2store.ext.audio - :members: diff --git a/docs/_sources/module_docs/py2store/ext/dataframes.rst.txt b/docs/_sources/module_docs/py2store/ext/dataframes.rst.txt deleted file mode 100644 index 5e1f2a7..0000000 --- a/docs/_sources/module_docs/py2store/ext/dataframes.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.dataframes -======================= -.. automodule:: py2store.ext.dataframes - :members: diff --git a/docs/_sources/module_docs/py2store/ext/docx.rst.txt b/docs/_sources/module_docs/py2store/ext/docx.rst.txt deleted file mode 100644 index 4ae6a5d..0000000 --- a/docs/_sources/module_docs/py2store/ext/docx.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.docx -================= -.. automodule:: py2store.ext.docx - :members: diff --git a/docs/_sources/module_docs/py2store/ext/github.rst.txt b/docs/_sources/module_docs/py2store/ext/github.rst.txt deleted file mode 100644 index a0b976f..0000000 --- a/docs/_sources/module_docs/py2store/ext/github.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.github -=================== -.. automodule:: py2store.ext.github - :members: diff --git a/docs/_sources/module_docs/py2store/ext/gitlab.rst.txt b/docs/_sources/module_docs/py2store/ext/gitlab.rst.txt deleted file mode 100644 index a8cd8d1..0000000 --- a/docs/_sources/module_docs/py2store/ext/gitlab.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.gitlab -=================== -.. automodule:: py2store.ext.gitlab - :members: diff --git a/docs/_sources/module_docs/py2store/ext/hdf.rst.txt b/docs/_sources/module_docs/py2store/ext/hdf.rst.txt deleted file mode 100644 index 3e58a93..0000000 --- a/docs/_sources/module_docs/py2store/ext/hdf.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.hdf -================ -.. automodule:: py2store.ext.hdf - :members: diff --git a/docs/_sources/module_docs/py2store/ext/kaggle.rst.txt b/docs/_sources/module_docs/py2store/ext/kaggle.rst.txt deleted file mode 100644 index 756379b..0000000 --- a/docs/_sources/module_docs/py2store/ext/kaggle.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.kaggle -=================== -.. automodule:: py2store.ext.kaggle - :members: diff --git a/docs/_sources/module_docs/py2store/ext/matlab.rst.txt b/docs/_sources/module_docs/py2store/ext/matlab.rst.txt deleted file mode 100644 index 5610eb4..0000000 --- a/docs/_sources/module_docs/py2store/ext/matlab.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.matlab -=================== -.. automodule:: py2store.ext.matlab - :members: diff --git a/docs/_sources/module_docs/py2store/ext/module_imports.rst.txt b/docs/_sources/module_docs/py2store/ext/module_imports.rst.txt deleted file mode 100644 index 61981f2..0000000 --- a/docs/_sources/module_docs/py2store/ext/module_imports.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.module_imports -=========================== -.. automodule:: py2store.ext.module_imports - :members: diff --git a/docs/_sources/module_docs/py2store/ext/wordnet.rst.txt b/docs/_sources/module_docs/py2store/ext/wordnet.rst.txt deleted file mode 100644 index d8cb690..0000000 --- a/docs/_sources/module_docs/py2store/ext/wordnet.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.wordnet -==================== -.. automodule:: py2store.ext.wordnet - :members: diff --git a/docs/_sources/module_docs/py2store/filesys.rst.txt b/docs/_sources/module_docs/py2store/filesys.rst.txt deleted file mode 100644 index ccd217b..0000000 --- a/docs/_sources/module_docs/py2store/filesys.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.filesys -================ -.. automodule:: py2store.filesys - :members: diff --git a/docs/_sources/module_docs/py2store/key_mappers.rst.txt b/docs/_sources/module_docs/py2store/key_mappers.rst.txt deleted file mode 100644 index b735bb9..0000000 --- a/docs/_sources/module_docs/py2store/key_mappers.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers -==================== -.. automodule:: py2store.key_mappers - :members: diff --git a/docs/_sources/module_docs/py2store/key_mappers/naming.rst.txt b/docs/_sources/module_docs/py2store/key_mappers/naming.rst.txt deleted file mode 100644 index e69557b..0000000 --- a/docs/_sources/module_docs/py2store/key_mappers/naming.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.naming -=========================== -.. automodule:: py2store.key_mappers.naming - :members: diff --git a/docs/_sources/module_docs/py2store/key_mappers/paths.rst.txt b/docs/_sources/module_docs/py2store/key_mappers/paths.rst.txt deleted file mode 100644 index d654d94..0000000 --- a/docs/_sources/module_docs/py2store/key_mappers/paths.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.paths -========================== -.. automodule:: py2store.key_mappers.paths - :members: diff --git a/docs/_sources/module_docs/py2store/key_mappers/str_utils.rst.txt b/docs/_sources/module_docs/py2store/key_mappers/str_utils.rst.txt deleted file mode 100644 index 781030b..0000000 --- a/docs/_sources/module_docs/py2store/key_mappers/str_utils.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.str_utils -============================== -.. automodule:: py2store.key_mappers.str_utils - :members: diff --git a/docs/_sources/module_docs/py2store/key_mappers/tuples.rst.txt b/docs/_sources/module_docs/py2store/key_mappers/tuples.rst.txt deleted file mode 100644 index aae63b9..0000000 --- a/docs/_sources/module_docs/py2store/key_mappers/tuples.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.tuples -=========================== -.. automodule:: py2store.key_mappers.tuples - :members: diff --git a/docs/_sources/module_docs/py2store/misc.rst.txt b/docs/_sources/module_docs/py2store/misc.rst.txt deleted file mode 100644 index 13e5be3..0000000 --- a/docs/_sources/module_docs/py2store/misc.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.misc -============= -.. automodule:: py2store.misc - :members: diff --git a/docs/_sources/module_docs/py2store/mixins.rst.txt b/docs/_sources/module_docs/py2store/mixins.rst.txt deleted file mode 100644 index d384129..0000000 --- a/docs/_sources/module_docs/py2store/mixins.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.mixins -=============== -.. automodule:: py2store.mixins - :members: diff --git a/docs/_sources/module_docs/py2store/my.rst.txt b/docs/_sources/module_docs/py2store/my.rst.txt deleted file mode 100644 index df5a72d..0000000 --- a/docs/_sources/module_docs/py2store/my.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.my -=========== -.. automodule:: py2store.my - :members: diff --git a/docs/_sources/module_docs/py2store/my/grabbers.rst.txt b/docs/_sources/module_docs/py2store/my/grabbers.rst.txt deleted file mode 100644 index 04d263c..0000000 --- a/docs/_sources/module_docs/py2store/my/grabbers.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.my.grabbers -==================== -.. automodule:: py2store.my.grabbers - :members: diff --git a/docs/_sources/module_docs/py2store/naming.rst.txt b/docs/_sources/module_docs/py2store/naming.rst.txt deleted file mode 100644 index 608b930..0000000 --- a/docs/_sources/module_docs/py2store/naming.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.naming -=============== -.. automodule:: py2store.naming - :members: diff --git a/docs/_sources/module_docs/py2store/parse_format.rst.txt b/docs/_sources/module_docs/py2store/parse_format.rst.txt deleted file mode 100644 index 1bca3a4..0000000 --- a/docs/_sources/module_docs/py2store/parse_format.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.parse_format -===================== -.. automodule:: py2store.parse_format - :members: diff --git a/docs/_sources/module_docs/py2store/paths.rst.txt b/docs/_sources/module_docs/py2store/paths.rst.txt deleted file mode 100644 index 595b16b..0000000 --- a/docs/_sources/module_docs/py2store/paths.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.paths -============== -.. automodule:: py2store.paths - :members: diff --git a/docs/_sources/module_docs/py2store/persisters.rst.txt b/docs/_sources/module_docs/py2store/persisters.rst.txt deleted file mode 100644 index d1d8fcf..0000000 --- a/docs/_sources/module_docs/py2store/persisters.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters -=================== -.. automodule:: py2store.persisters - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst.txt b/docs/_sources/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst.txt deleted file mode 100644 index 3f0e617..0000000 --- a/docs/_sources/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters._postgres_w_psycopg2_in_progress -==================================================== -.. automodule:: py2store.persisters._postgres_w_psycopg2_in_progress - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/arangodb_w_pyarango.rst.txt b/docs/_sources/module_docs/py2store/persisters/arangodb_w_pyarango.rst.txt deleted file mode 100644 index e99d97e..0000000 --- a/docs/_sources/module_docs/py2store/persisters/arangodb_w_pyarango.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.arangodb_w_pyarango -======================================= -.. automodule:: py2store.persisters.arangodb_w_pyarango - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/couchdb_w_couchdb.rst.txt b/docs/_sources/module_docs/py2store/persisters/couchdb_w_couchdb.rst.txt deleted file mode 100644 index 567d04a..0000000 --- a/docs/_sources/module_docs/py2store/persisters/couchdb_w_couchdb.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.couchdb_w_couchdb -===================================== -.. automodule:: py2store.persisters.couchdb_w_couchdb - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/dropbox_w_dropbox.rst.txt b/docs/_sources/module_docs/py2store/persisters/dropbox_w_dropbox.rst.txt deleted file mode 100644 index 5d5b625..0000000 --- a/docs/_sources/module_docs/py2store/persisters/dropbox_w_dropbox.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dropbox_w_dropbox -===================================== -.. automodule:: py2store.persisters.dropbox_w_dropbox - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/dropbox_w_requests.rst.txt b/docs/_sources/module_docs/py2store/persisters/dropbox_w_requests.rst.txt deleted file mode 100644 index 0e5bf8f..0000000 --- a/docs/_sources/module_docs/py2store/persisters/dropbox_w_requests.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dropbox_w_requests -====================================== -.. automodule:: py2store.persisters.dropbox_w_requests - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/dropbox_w_urllib.rst.txt b/docs/_sources/module_docs/py2store/persisters/dropbox_w_urllib.rst.txt deleted file mode 100644 index 7e700d9..0000000 --- a/docs/_sources/module_docs/py2store/persisters/dropbox_w_urllib.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dropbox_w_urllib -==================================== -.. automodule:: py2store.persisters.dropbox_w_urllib - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/dynamodb_w_boto3.rst.txt b/docs/_sources/module_docs/py2store/persisters/dynamodb_w_boto3.rst.txt deleted file mode 100644 index 367f6b7..0000000 --- a/docs/_sources/module_docs/py2store/persisters/dynamodb_w_boto3.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dynamodb_w_boto3 -==================================== -.. automodule:: py2store.persisters.dynamodb_w_boto3 - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/ftp_persister.rst.txt b/docs/_sources/module_docs/py2store/persisters/ftp_persister.rst.txt deleted file mode 100644 index d13ef0d..0000000 --- a/docs/_sources/module_docs/py2store/persisters/ftp_persister.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.ftp_persister -================================= -.. automodule:: py2store.persisters.ftp_persister - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/googledrive_w_pydrive.rst.txt b/docs/_sources/module_docs/py2store/persisters/googledrive_w_pydrive.rst.txt deleted file mode 100644 index 773b1cb..0000000 --- a/docs/_sources/module_docs/py2store/persisters/googledrive_w_pydrive.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.googledrive_w_pydrive -========================================= -.. automodule:: py2store.persisters.googledrive_w_pydrive - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/local_files.rst.txt b/docs/_sources/module_docs/py2store/persisters/local_files.rst.txt deleted file mode 100644 index e2c9a1e..0000000 --- a/docs/_sources/module_docs/py2store/persisters/local_files.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.local_files -=============================== -.. automodule:: py2store.persisters.local_files - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/mongo_w_pymongo.rst.txt b/docs/_sources/module_docs/py2store/persisters/mongo_w_pymongo.rst.txt deleted file mode 100644 index 49e503b..0000000 --- a/docs/_sources/module_docs/py2store/persisters/mongo_w_pymongo.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.mongo_w_pymongo -=================================== -.. automodule:: py2store.persisters.mongo_w_pymongo - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/new_s3.rst.txt b/docs/_sources/module_docs/py2store/persisters/new_s3.rst.txt deleted file mode 100644 index 9dc0165..0000000 --- a/docs/_sources/module_docs/py2store/persisters/new_s3.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.new_s3 -========================== -.. automodule:: py2store.persisters.new_s3 - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/redis_w_redis.rst.txt b/docs/_sources/module_docs/py2store/persisters/redis_w_redis.rst.txt deleted file mode 100644 index c493e04..0000000 --- a/docs/_sources/module_docs/py2store/persisters/redis_w_redis.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.redis_w_redis -================================= -.. automodule:: py2store.persisters.redis_w_redis - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/s3_w_boto3.rst.txt b/docs/_sources/module_docs/py2store/persisters/s3_w_boto3.rst.txt deleted file mode 100644 index 7d4d2ab..0000000 --- a/docs/_sources/module_docs/py2store/persisters/s3_w_boto3.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.s3_w_boto3 -============================== -.. automodule:: py2store.persisters.s3_w_boto3 - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/sql_w_odbc.rst.txt b/docs/_sources/module_docs/py2store/persisters/sql_w_odbc.rst.txt deleted file mode 100644 index 535b0a3..0000000 --- a/docs/_sources/module_docs/py2store/persisters/sql_w_odbc.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.sql_w_odbc -============================== -.. automodule:: py2store.persisters.sql_w_odbc - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/sql_w_sqlalchemy.rst.txt b/docs/_sources/module_docs/py2store/persisters/sql_w_sqlalchemy.rst.txt deleted file mode 100644 index c45bd23..0000000 --- a/docs/_sources/module_docs/py2store/persisters/sql_w_sqlalchemy.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.sql_w_sqlalchemy -==================================== -.. automodule:: py2store.persisters.sql_w_sqlalchemy - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/ssh_persister.rst.txt b/docs/_sources/module_docs/py2store/persisters/ssh_persister.rst.txt deleted file mode 100644 index d3dc03e..0000000 --- a/docs/_sources/module_docs/py2store/persisters/ssh_persister.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.ssh_persister -================================= -.. automodule:: py2store.persisters.ssh_persister - :members: diff --git a/docs/_sources/module_docs/py2store/persisters/w_aiofile.rst.txt b/docs/_sources/module_docs/py2store/persisters/w_aiofile.rst.txt deleted file mode 100644 index 55495f1..0000000 --- a/docs/_sources/module_docs/py2store/persisters/w_aiofile.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.w_aiofile -============================= -.. automodule:: py2store.persisters.w_aiofile - :members: diff --git a/docs/_sources/module_docs/py2store/scrap/new_gen_local.rst.txt b/docs/_sources/module_docs/py2store/scrap/new_gen_local.rst.txt deleted file mode 100644 index 7fdd498..0000000 --- a/docs/_sources/module_docs/py2store/scrap/new_gen_local.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.scrap.new_gen_local -============================ -.. automodule:: py2store.scrap.new_gen_local - :members: diff --git a/docs/_sources/module_docs/py2store/serializers.rst.txt b/docs/_sources/module_docs/py2store/serializers.rst.txt deleted file mode 100644 index 50e0f0c..0000000 --- a/docs/_sources/module_docs/py2store/serializers.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers -==================== -.. automodule:: py2store.serializers - :members: diff --git a/docs/_sources/module_docs/py2store/serializers/audio.rst.txt b/docs/_sources/module_docs/py2store/serializers/audio.rst.txt deleted file mode 100644 index aea18fc..0000000 --- a/docs/_sources/module_docs/py2store/serializers/audio.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.audio -========================== -.. automodule:: py2store.serializers.audio - :members: diff --git a/docs/_sources/module_docs/py2store/serializers/jsonization.rst.txt b/docs/_sources/module_docs/py2store/serializers/jsonization.rst.txt deleted file mode 100644 index 5213326..0000000 --- a/docs/_sources/module_docs/py2store/serializers/jsonization.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.jsonization -================================ -.. automodule:: py2store.serializers.jsonization - :members: diff --git a/docs/_sources/module_docs/py2store/serializers/pickled.rst.txt b/docs/_sources/module_docs/py2store/serializers/pickled.rst.txt deleted file mode 100644 index 8504d7c..0000000 --- a/docs/_sources/module_docs/py2store/serializers/pickled.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.pickled -============================ -.. automodule:: py2store.serializers.pickled - :members: diff --git a/docs/_sources/module_docs/py2store/serializers/regular_panel_data.rst.txt b/docs/_sources/module_docs/py2store/serializers/regular_panel_data.rst.txt deleted file mode 100644 index 3ad84b1..0000000 --- a/docs/_sources/module_docs/py2store/serializers/regular_panel_data.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.regular_panel_data -======================================= -.. automodule:: py2store.serializers.regular_panel_data - :members: diff --git a/docs/_sources/module_docs/py2store/serializers/sequential.rst.txt b/docs/_sources/module_docs/py2store/serializers/sequential.rst.txt deleted file mode 100644 index 1e9999a..0000000 --- a/docs/_sources/module_docs/py2store/serializers/sequential.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.sequential -=============================== -.. automodule:: py2store.serializers.sequential - :members: diff --git a/docs/_sources/module_docs/py2store/signatures.rst.txt b/docs/_sources/module_docs/py2store/signatures.rst.txt deleted file mode 100644 index fd0c880..0000000 --- a/docs/_sources/module_docs/py2store/signatures.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.signatures -=================== -.. automodule:: py2store.signatures - :members: diff --git a/docs/_sources/module_docs/py2store/slib.rst.txt b/docs/_sources/module_docs/py2store/slib.rst.txt deleted file mode 100644 index 73997c3..0000000 --- a/docs/_sources/module_docs/py2store/slib.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.slib -============= -.. automodule:: py2store.slib - :members: diff --git a/docs/_sources/module_docs/py2store/slib/s_configparser.rst.txt b/docs/_sources/module_docs/py2store/slib/s_configparser.rst.txt deleted file mode 100644 index 6f80aef..0000000 --- a/docs/_sources/module_docs/py2store/slib/s_configparser.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.slib.s_configparser -============================ -.. automodule:: py2store.slib.s_configparser - :members: diff --git a/docs/_sources/module_docs/py2store/slib/s_zipfile.rst.txt b/docs/_sources/module_docs/py2store/slib/s_zipfile.rst.txt deleted file mode 100644 index 006b836..0000000 --- a/docs/_sources/module_docs/py2store/slib/s_zipfile.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.slib.s_zipfile -======================= -.. automodule:: py2store.slib.s_zipfile - :members: diff --git a/docs/_sources/module_docs/py2store/sources.rst.txt b/docs/_sources/module_docs/py2store/sources.rst.txt deleted file mode 100644 index f4b2f9a..0000000 --- a/docs/_sources/module_docs/py2store/sources.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.sources -================ -.. automodule:: py2store.sources - :members: diff --git a/docs/_sources/module_docs/py2store/stores.rst.txt b/docs/_sources/module_docs/py2store/stores.rst.txt deleted file mode 100644 index f3044d0..0000000 --- a/docs/_sources/module_docs/py2store/stores.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores -=============== -.. automodule:: py2store.stores - :members: diff --git a/docs/_sources/module_docs/py2store/stores/arangodb_store.rst.txt b/docs/_sources/module_docs/py2store/stores/arangodb_store.rst.txt deleted file mode 100644 index 412ab51..0000000 --- a/docs/_sources/module_docs/py2store/stores/arangodb_store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.arangodb_store -============================== -.. automodule:: py2store.stores.arangodb_store - :members: diff --git a/docs/_sources/module_docs/py2store/stores/couchdb_store.rst.txt b/docs/_sources/module_docs/py2store/stores/couchdb_store.rst.txt deleted file mode 100644 index 8b28730..0000000 --- a/docs/_sources/module_docs/py2store/stores/couchdb_store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.couchdb_store -============================= -.. automodule:: py2store.stores.couchdb_store - :members: diff --git a/docs/_sources/module_docs/py2store/stores/delegation_stores.rst.txt b/docs/_sources/module_docs/py2store/stores/delegation_stores.rst.txt deleted file mode 100644 index 410755f..0000000 --- a/docs/_sources/module_docs/py2store/stores/delegation_stores.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.delegation_stores -================================= -.. automodule:: py2store.stores.delegation_stores - :members: diff --git a/docs/_sources/module_docs/py2store/stores/dropbox_store.rst.txt b/docs/_sources/module_docs/py2store/stores/dropbox_store.rst.txt deleted file mode 100644 index 6daec65..0000000 --- a/docs/_sources/module_docs/py2store/stores/dropbox_store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.dropbox_store -============================= -.. automodule:: py2store.stores.dropbox_store - :members: diff --git a/docs/_sources/module_docs/py2store/stores/local_store.rst.txt b/docs/_sources/module_docs/py2store/stores/local_store.rst.txt deleted file mode 100644 index 38cad28..0000000 --- a/docs/_sources/module_docs/py2store/stores/local_store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.local_store -=========================== -.. automodule:: py2store.stores.local_store - :members: diff --git a/docs/_sources/module_docs/py2store/stores/mongo_store.rst.txt b/docs/_sources/module_docs/py2store/stores/mongo_store.rst.txt deleted file mode 100644 index f9da813..0000000 --- a/docs/_sources/module_docs/py2store/stores/mongo_store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.mongo_store -=========================== -.. automodule:: py2store.stores.mongo_store - :members: diff --git a/docs/_sources/module_docs/py2store/stores/s3_store.rst.txt b/docs/_sources/module_docs/py2store/stores/s3_store.rst.txt deleted file mode 100644 index f93f4d1..0000000 --- a/docs/_sources/module_docs/py2store/stores/s3_store.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.s3_store -======================== -.. automodule:: py2store.stores.s3_store - :members: diff --git a/docs/_sources/module_docs/py2store/stores/sql_w_sqlalchemy.rst.txt b/docs/_sources/module_docs/py2store/stores/sql_w_sqlalchemy.rst.txt deleted file mode 100644 index 2e33988..0000000 --- a/docs/_sources/module_docs/py2store/stores/sql_w_sqlalchemy.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.sql_w_sqlalchemy -================================ -.. automodule:: py2store.stores.sql_w_sqlalchemy - :members: diff --git a/docs/_sources/module_docs/py2store/test.rst.txt b/docs/_sources/module_docs/py2store/test.rst.txt deleted file mode 100644 index a42a106..0000000 --- a/docs/_sources/module_docs/py2store/test.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test -============= -.. automodule:: py2store.test - :members: diff --git a/docs/_sources/module_docs/py2store/test/local_files_test.rst.txt b/docs/_sources/module_docs/py2store/test/local_files_test.rst.txt deleted file mode 100644 index 16f9f49..0000000 --- a/docs/_sources/module_docs/py2store/test/local_files_test.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.local_files_test -============================== -.. automodule:: py2store.test.local_files_test - :members: diff --git a/docs/_sources/module_docs/py2store/test/quick_test.rst.txt b/docs/_sources/module_docs/py2store/test/quick_test.rst.txt deleted file mode 100644 index a7585cc..0000000 --- a/docs/_sources/module_docs/py2store/test/quick_test.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.quick_test -======================== -.. automodule:: py2store.test.quick_test - :members: diff --git a/docs/_sources/module_docs/py2store/test/scrap.rst.txt b/docs/_sources/module_docs/py2store/test/scrap.rst.txt deleted file mode 100644 index ff6d70e..0000000 --- a/docs/_sources/module_docs/py2store/test/scrap.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.scrap -=================== -.. automodule:: py2store.test.scrap - :members: diff --git a/docs/_sources/module_docs/py2store/test/simple_test.rst.txt b/docs/_sources/module_docs/py2store/test/simple_test.rst.txt deleted file mode 100644 index 3953ebc..0000000 --- a/docs/_sources/module_docs/py2store/test/simple_test.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.simple_test -========================= -.. automodule:: py2store.test.simple_test - :members: diff --git a/docs/_sources/module_docs/py2store/test/trans_test.rst.txt b/docs/_sources/module_docs/py2store/test/trans_test.rst.txt deleted file mode 100644 index 8b8cca2..0000000 --- a/docs/_sources/module_docs/py2store/test/trans_test.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.trans_test -======================== -.. automodule:: py2store.test.trans_test - :members: diff --git a/docs/_sources/module_docs/py2store/test/util.rst.txt b/docs/_sources/module_docs/py2store/test/util.rst.txt deleted file mode 100644 index c4f99b2..0000000 --- a/docs/_sources/module_docs/py2store/test/util.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.util -================== -.. automodule:: py2store.test.util - :members: diff --git a/docs/_sources/module_docs/py2store/trans.rst.txt b/docs/_sources/module_docs/py2store/trans.rst.txt deleted file mode 100644 index e5d9c12..0000000 --- a/docs/_sources/module_docs/py2store/trans.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.trans -============== -.. automodule:: py2store.trans - :members: diff --git a/docs/_sources/module_docs/py2store/util.rst.txt b/docs/_sources/module_docs/py2store/util.rst.txt deleted file mode 100644 index aeedca3..0000000 --- a/docs/_sources/module_docs/py2store/util.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.util -============= -.. automodule:: py2store.util - :members: diff --git a/docs/_sources/module_docs/py2store/utils.rst.txt b/docs/_sources/module_docs/py2store/utils.rst.txt deleted file mode 100644 index 60edf44..0000000 --- a/docs/_sources/module_docs/py2store/utils.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils -============== -.. automodule:: py2store.utils - :members: diff --git a/docs/_sources/module_docs/py2store/utils/affine_conversion.rst.txt b/docs/_sources/module_docs/py2store/utils/affine_conversion.rst.txt deleted file mode 100644 index 7838f38..0000000 --- a/docs/_sources/module_docs/py2store/utils/affine_conversion.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.affine_conversion -================================ -.. automodule:: py2store.utils.affine_conversion - :members: diff --git a/docs/_sources/module_docs/py2store/utils/appendable.rst.txt b/docs/_sources/module_docs/py2store/utils/appendable.rst.txt deleted file mode 100644 index 68d3310..0000000 --- a/docs/_sources/module_docs/py2store/utils/appendable.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.appendable -========================= -.. automodule:: py2store.utils.appendable - :members: diff --git a/docs/_sources/module_docs/py2store/utils/attr_dict.rst.txt b/docs/_sources/module_docs/py2store/utils/attr_dict.rst.txt deleted file mode 100644 index 70d27c9..0000000 --- a/docs/_sources/module_docs/py2store/utils/attr_dict.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.attr_dict -======================== -.. automodule:: py2store.utils.attr_dict - :members: diff --git a/docs/_sources/module_docs/py2store/utils/cache_descriptors.rst.txt b/docs/_sources/module_docs/py2store/utils/cache_descriptors.rst.txt deleted file mode 100644 index 0c2ca58..0000000 --- a/docs/_sources/module_docs/py2store/utils/cache_descriptors.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.cache_descriptors -================================ -.. automodule:: py2store.utils.cache_descriptors - :members: diff --git a/docs/_sources/module_docs/py2store/utils/cumul_aggreg_write.rst.txt b/docs/_sources/module_docs/py2store/utils/cumul_aggreg_write.rst.txt deleted file mode 100644 index 468131e..0000000 --- a/docs/_sources/module_docs/py2store/utils/cumul_aggreg_write.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.cumul_aggreg_write -================================= -.. automodule:: py2store.utils.cumul_aggreg_write - :members: diff --git a/docs/_sources/module_docs/py2store/utils/explicit.rst.txt b/docs/_sources/module_docs/py2store/utils/explicit.rst.txt deleted file mode 100644 index ddbfe2c..0000000 --- a/docs/_sources/module_docs/py2store/utils/explicit.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.explicit -======================= -.. automodule:: py2store.utils.explicit - :members: diff --git a/docs/_sources/module_docs/py2store/utils/glom.rst.txt b/docs/_sources/module_docs/py2store/utils/glom.rst.txt deleted file mode 100644 index f911226..0000000 --- a/docs/_sources/module_docs/py2store/utils/glom.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.glom -=================== -.. automodule:: py2store.utils.glom - :members: diff --git a/docs/_sources/module_docs/py2store/utils/mappify.rst.txt b/docs/_sources/module_docs/py2store/utils/mappify.rst.txt deleted file mode 100644 index a67657f..0000000 --- a/docs/_sources/module_docs/py2store/utils/mappify.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.mappify -====================== -.. automodule:: py2store.utils.mappify - :members: diff --git a/docs/_sources/module_docs/py2store/utils/mg_selectors.rst.txt b/docs/_sources/module_docs/py2store/utils/mg_selectors.rst.txt deleted file mode 100644 index d60184a..0000000 --- a/docs/_sources/module_docs/py2store/utils/mg_selectors.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.mg_selectors -=========================== -.. automodule:: py2store.utils.mg_selectors - :members: diff --git a/docs/_sources/module_docs/py2store/utils/mongoquery.rst.txt b/docs/_sources/module_docs/py2store/utils/mongoquery.rst.txt deleted file mode 100644 index 811d593..0000000 --- a/docs/_sources/module_docs/py2store/utils/mongoquery.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.mongoquery -========================= -.. automodule:: py2store.utils.mongoquery - :members: diff --git a/docs/_sources/module_docs/py2store/utils/signatures.rst.txt b/docs/_sources/module_docs/py2store/utils/signatures.rst.txt deleted file mode 100644 index 86d6bf3..0000000 --- a/docs/_sources/module_docs/py2store/utils/signatures.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.signatures -========================= -.. automodule:: py2store.utils.signatures - :members: diff --git a/docs/_sources/module_docs/py2store/utils/sliceable.rst.txt b/docs/_sources/module_docs/py2store/utils/sliceable.rst.txt deleted file mode 100644 index d3951af..0000000 --- a/docs/_sources/module_docs/py2store/utils/sliceable.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.sliceable -======================== -.. automodule:: py2store.utils.sliceable - :members: diff --git a/docs/_sources/module_docs/py2store/utils/timeseries_caching.rst.txt b/docs/_sources/module_docs/py2store/utils/timeseries_caching.rst.txt deleted file mode 100644 index 83598a1..0000000 --- a/docs/_sources/module_docs/py2store/utils/timeseries_caching.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.timeseries_caching -================================= -.. automodule:: py2store.utils.timeseries_caching - :members: diff --git a/docs/_sources/module_docs/py2store/utils/uri_utils.rst.txt b/docs/_sources/module_docs/py2store/utils/uri_utils.rst.txt deleted file mode 100644 index d60c81e..0000000 --- a/docs/_sources/module_docs/py2store/utils/uri_utils.rst.txt +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.uri_utils -======================== -.. automodule:: py2store.utils.uri_utils - :members: diff --git a/docs/_sources/table_of_contents.rst.txt b/docs/_sources/table_of_contents.rst.txt deleted file mode 100644 index 7796848..0000000 --- a/docs/_sources/table_of_contents.rst.txt +++ /dev/null @@ -1,82 +0,0 @@ -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - module_docs/py2store - module_docs/py2store/access - module_docs/py2store/appendable - module_docs/py2store/base - module_docs/py2store/caching - module_docs/py2store/core - module_docs/py2store/dig - module_docs/py2store/errors - module_docs/py2store/examples - module_docs/py2store/examples/dropbox_w_urllib - module_docs/py2store/examples/kv_walking - module_docs/py2store/examples/last_key_inserted - module_docs/py2store/examples/python_code_stats - module_docs/py2store/examples/write_caches - module_docs/py2store/ext - module_docs/py2store/ext/dataframes - module_docs/py2store/ext/docx - module_docs/py2store/ext/github - module_docs/py2store/ext/gitlab - module_docs/py2store/ext/hdf - module_docs/py2store/ext/matlab - module_docs/py2store/ext/wordnet - module_docs/py2store/filesys - module_docs/py2store/key_mappers - module_docs/py2store/key_mappers/naming - module_docs/py2store/key_mappers/paths - module_docs/py2store/key_mappers/str_utils - module_docs/py2store/key_mappers/tuples - module_docs/py2store/misc - module_docs/py2store/mixins - module_docs/py2store/my - module_docs/py2store/my/grabbers - module_docs/py2store/naming - module_docs/py2store/parse_format - module_docs/py2store/paths - module_docs/py2store/persisters - module_docs/py2store/persisters/dropbox_w_dropbox - module_docs/py2store/persisters/googledrive_w_pydrive - module_docs/py2store/persisters/local_files - module_docs/py2store/persisters/new_s3 - module_docs/py2store/persisters/redis_w_redis - module_docs/py2store/persisters/s3_w_boto3 - module_docs/py2store/persisters/sql_w_sqlalchemy - module_docs/py2store/persisters/w_aiofile - module_docs/py2store/serializers - module_docs/py2store/serializers/pickled - module_docs/py2store/signatures - module_docs/py2store/slib - module_docs/py2store/slib/s_configparser - module_docs/py2store/slib/s_zipfile - module_docs/py2store/sources - module_docs/py2store/stores - module_docs/py2store/stores/dropbox_store - module_docs/py2store/stores/local_store - module_docs/py2store/stores/s3_store - module_docs/py2store/stores/sql_w_sqlalchemy - module_docs/py2store/test - module_docs/py2store/test/local_files_test - module_docs/py2store/test/quick_test - module_docs/py2store/test/scrap - module_docs/py2store/test/util - module_docs/py2store/trans - module_docs/py2store/util - module_docs/py2store/utils - module_docs/py2store/utils/affine_conversion - module_docs/py2store/utils/appendable - module_docs/py2store/utils/attr_dict - module_docs/py2store/utils/cache_descriptors - module_docs/py2store/utils/cumul_aggreg_write - module_docs/py2store/utils/explicit - module_docs/py2store/utils/glom - module_docs/py2store/utils/mappify - module_docs/py2store/utils/mg_selectors - module_docs/py2store/utils/mongoquery - module_docs/py2store/utils/signatures - module_docs/py2store/utils/sliceable - module_docs/py2store/utils/timeseries_caching - module_docs/py2store/utils/uri_utils diff --git a/docs/_sources/test.rst.txt b/docs/_sources/test.rst.txt deleted file mode 100644 index aabeb77..0000000 --- a/docs/_sources/test.rst.txt +++ /dev/null @@ -1,494 +0,0 @@ -py2store.filesys -================ -.. automodule:: py2store.filesys - :members: - -py2store.misc -============= -.. automodule:: py2store.misc - :members: - -py2store.mixins -=============== -.. automodule:: py2store.mixins - :members: - -py2store.test.util -================== -.. automodule:: py2store.test.util - :members: - -py2store.test.quick -=================== -.. automodule:: py2store.test.quick - :members: - -py2store.test -============= -.. automodule:: py2store.test - :members: - -py2store.test.simple -==================== -.. automodule:: py2store.test.simple - :members: - -py2store.test.scrap -=================== -.. automodule:: py2store.test.scrap - :members: - -py2store.util -============= -.. automodule:: py2store.util - :members: - -py2store.ext.docx -================= -.. automodule:: py2store.ext.docx - :members: - -py2store.ext.gitlab -=================== -.. automodule:: py2store.ext.gitlab - :members: - -py2store.ext.hdf -================ -.. automodule:: py2store.ext.hdf - :members: - -py2store.ext -============ -.. automodule:: py2store.ext - :members: - -py2store.ext.matlab -=================== -.. automodule:: py2store.ext.matlab - :members: - -py2store.ext.kaggle -=================== -.. automodule:: py2store.ext.kaggle - :members: - -py2store.ext.module_imports -=========================== -.. automodule:: py2store.ext.module_imports - :members: - -py2store.ext.audio -================== -.. automodule:: py2store.ext.audio - :members: - -py2store.ext.github -=================== -.. automodule:: py2store.ext.github - :members: - -py2store.ext.dataframes -======================= -.. automodule:: py2store.ext.dataframes - :members: - -py2store.access -=============== -.. automodule:: py2store.access - :members: - -py2store.__init__ -================= -.. automodule:: py2store.__init__ - :members: - -py2store.stores.s3_store -======================== -.. automodule:: py2store.stores.s3_store - :members: - -py2store.stores.delegation_stores -================================= -.. automodule:: py2store.stores.delegation_stores - :members: - -py2store.stores.sql_w_sqlalchemy -================================ -.. automodule:: py2store.stores.sql_w_sqlalchemy - :members: - -py2store.stores.arangodb_store -============================== -.. automodule:: py2store.stores.arangodb_store - :members: - -py2store.stores.dropbox_store -============================= -.. automodule:: py2store.stores.dropbox_store - :members: - -py2store.stores.local_store -=========================== -.. automodule:: py2store.stores.local_store - :members: - -py2store.stores -=============== -.. automodule:: py2store.stores - :members: - -py2store.stores.couchdb_store -============================= -.. automodule:: py2store.stores.couchdb_store - :members: - -py2store.stores.mongo_store -=========================== -.. automodule:: py2store.stores.mongo_store - :members: - -py2store.core -============= -.. automodule:: py2store.core - :members: - -py2store.utils.uri_utils -======================== -.. automodule:: py2store.utils.uri_utils - :members: - -py2store.utils.explicit -======================= -.. automodule:: py2store.utils.explicit - :members: - -py2store.utils.timeseries_caching -================================= -.. automodule:: py2store.utils.timeseries_caching - :members: - -py2store.utils.attr_dict.py.attr_dict -===================================== -.. automodule:: py2store.utils.attr_dict.py.attr_dict - :members: - -py2store.utils.attr_dict.py -=========================== -.. automodule:: py2store.utils.attr_dict.py - :members: - -py2store.utils.cumul_aggreg_write -================================= -.. automodule:: py2store.utils.cumul_aggreg_write - :members: - -py2store.utils -============== -.. automodule:: py2store.utils - :members: - -py2store.utils.cache_descriptors -================================ -.. automodule:: py2store.utils.cache_descriptors - :members: - -py2store.utils.appendable -========================= -.. automodule:: py2store.utils.appendable - :members: - -py2store.utils.affine_conversion -================================ -.. automodule:: py2store.utils.affine_conversion - :members: - -py2store.utils.signatures -========================= -.. automodule:: py2store.utils.signatures - :members: - -py2store.utils.sliceable -======================== -.. automodule:: py2store.utils.sliceable - :members: - -py2store.utils.mappify -====================== -.. automodule:: py2store.utils.mappify - :members: - -py2store.utils.glom -=================== -.. automodule:: py2store.utils.glom - :members: - -py2store.persisters.sql_w_odbc -============================== -.. automodule:: py2store.persisters.sql_w_odbc - :members: - -py2store.persisters.dynamodb_w_boto3 -==================================== -.. automodule:: py2store.persisters.dynamodb_w_boto3 - :members: - -py2store.persisters.couchdb_w_couchdb -===================================== -.. automodule:: py2store.persisters.couchdb_w_couchdb - :members: - -py2store.persisters.ftp_persister -================================= -.. automodule:: py2store.persisters.ftp_persister - :members: - -py2store.persisters.dropbox_w_urllib -==================================== -.. automodule:: py2store.persisters.dropbox_w_urllib - :members: - -py2store.persisters._google_drive_in_progress -============================================= -.. automodule:: py2store.persisters._google_drive_in_progress - :members: - -py2store.persisters.dropbox_w_dropbox -===================================== -.. automodule:: py2store.persisters.dropbox_w_dropbox - :members: - -py2store.persisters.redis_w_redis -================================= -.. automodule:: py2store.persisters.redis_w_redis - :members: - -py2store.persisters.sql_w_sqlalchemy -==================================== -.. automodule:: py2store.persisters.sql_w_sqlalchemy - :members: - -py2store.persisters.new_s3 -========================== -.. automodule:: py2store.persisters.new_s3 - :members: - -py2store.persisters -=================== -.. automodule:: py2store.persisters - :members: - -py2store.persisters.dropbox_w_requests -====================================== -.. automodule:: py2store.persisters.dropbox_w_requests - :members: - -py2store.persisters.w_aiofile -============================= -.. automodule:: py2store.persisters.w_aiofile - :members: - -py2store.persisters.local_files -=============================== -.. automodule:: py2store.persisters.local_files - :members: - -py2store.persisters.arangodb_w_pyarango -======================================= -.. automodule:: py2store.persisters.arangodb_w_pyarango - :members: - -py2store.persisters._cassandra_in_progress -========================================== -.. automodule:: py2store.persisters._cassandra_in_progress - :members: - -py2store.persisters._couchdb_in_progress -======================================== -.. automodule:: py2store.persisters._couchdb_in_progress - :members: - -py2store.persisters.s3_w_boto3 -============================== -.. automodule:: py2store.persisters.s3_w_boto3 - :members: - -py2store.persisters._postgres_w_psycopg2_in_progress -==================================================== -.. automodule:: py2store.persisters._postgres_w_psycopg2_in_progress - :members: - -py2store.persisters.ssh_persister -================================= -.. automodule:: py2store.persisters.ssh_persister - :members: - -py2store.persisters.mongo_w_pymongo -=================================== -.. automodule:: py2store.persisters.mongo_w_pymongo - :members: - -py2store.persisters.googledrive_w_pydrive -========================================= -.. automodule:: py2store.persisters.googledrive_w_pydrive - :members: - -py2store.sources -================ -.. automodule:: py2store.sources - :members: - -py2store.dig -============ -.. automodule:: py2store.dig - :members: - -py2store.serializers.pickled -============================ -.. automodule:: py2store.serializers.pickled - :members: - -py2store.serializers.jsonization -================================ -.. automodule:: py2store.serializers.jsonization - :members: - -py2store.serializers -==================== -.. automodule:: py2store.serializers - :members: - -py2store.serializers.sequential -=============================== -.. automodule:: py2store.serializers.sequential - :members: - -py2store.serializers.regular_panel_data -======================================= -.. automodule:: py2store.serializers.regular_panel_data - :members: - -py2store.serializers.audio -========================== -.. automodule:: py2store.serializers.audio - :members: - -py2store.caching -================ -.. automodule:: py2store.caching - :members: - -py2store.scrap -============== -.. automodule:: py2store.scrap - :members: - -py2store.scrap.new_gen_local -============================ -.. automodule:: py2store.scrap.new_gen_local - :members: - -py2store.examples.write_caches -============================== -.. automodule:: py2store.examples.write_caches - :members: - -py2store.examples -================= -.. automodule:: py2store.examples - :members: - -py2store.examples.python_code_stats -=================================== -.. automodule:: py2store.examples.python_code_stats - :members: - -py2store.examples.kv_walking -============================ -.. automodule:: py2store.examples.kv_walking - :members: - -py2store.my -=========== -.. automodule:: py2store.my - :members: - -py2store.my.grabbers -==================== -.. automodule:: py2store.my.grabbers - :members: - -py2store.trans -============== -.. automodule:: py2store.trans - :members: - -py2store.key_mappers.str_utils -============================== -.. automodule:: py2store.key_mappers.str_utils - :members: - -py2store.key_mappers.tuples -=========================== -.. automodule:: py2store.key_mappers.tuples - :members: - -py2store.key_mappers.paths -========================== -.. automodule:: py2store.key_mappers.paths - :members: - -py2store.key_mappers.naming -=========================== -.. automodule:: py2store.key_mappers.naming - :members: - -py2store.key_mappers -==================== -.. automodule:: py2store.key_mappers - :members: - -py2store.errors -=============== -.. automodule:: py2store.errors - :members: - -py2store.slib.s_configparser -============================ -.. automodule:: py2store.slib.s_configparser - :members: - -py2store.slib -============= -.. automodule:: py2store.slib - :members: - -py2store.slib.s_zipfile -======================= -.. automodule:: py2store.slib.s_zipfile - :members: - -py2store.base -============= -.. automodule:: py2store.base - :members: - -py2store.selectors.mg_selectors -=============================== -.. automodule:: py2store.selectors.mg_selectors - :members: - -py2store.selectors.mongoquery -============================= -.. automodule:: py2store.selectors.mongoquery - :members: - -py2store.selectors -================== -.. automodule:: py2store.selectors - :members: - -py2store.parse_format -===================== -.. automodule:: py2store.parse_format - :members: diff --git a/docs/_static/alabaster.css b/docs/_static/alabaster.css deleted file mode 100644 index 0eddaeb..0000000 --- a/docs/_static/alabaster.css +++ /dev/null @@ -1,701 +0,0 @@ -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Georgia, serif; - font-size: 17px; - background-color: #fff; - color: #000; - margin: 0; - padding: 0; -} - - -div.document { - width: 940px; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 220px; -} - -div.sphinxsidebar { - width: 220px; - font-size: 14px; - line-height: 1.5; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #fff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -div.body > .section { - text-align: left; -} - -div.footer { - width: 940px; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -p.caption { - font-family: inherit; - font-size: inherit; -} - - -div.relations { - display: none; -} - - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0; - margin: -10px 0 0 0px; - text-align: center; -} - -div.sphinxsidebarwrapper h1.logo { - margin-top: -10px; - text-align: center; - margin-bottom: 5px; - text-align: left; -} - -div.sphinxsidebarwrapper h1.logo-name { - margin-top: 0px; -} - -div.sphinxsidebarwrapper p.blurb { - margin-top: 0; - font-style: normal; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Georgia, serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar ul li.toctree-l1 > a { - font-size: 120%; -} - -div.sphinxsidebar ul li.toctree-l2 > a { - font-size: 110%; -} - -div.sphinxsidebar input { - border: 1px solid #CCC; - font-family: Georgia, serif; - font-size: 1em; -} - -div.sphinxsidebar hr { - border: none; - height: 1px; - color: #AAA; - background: #AAA; - - text-align: left; - margin-left: 0; - width: 50%; -} - -div.sphinxsidebar .badge { - border-bottom: none; -} - -div.sphinxsidebar .badge:hover { - border-bottom: none; -} - -/* To address an issue with donation coming after search */ -div.sphinxsidebar h3.donation { - margin-top: 10px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Georgia, serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #DDD; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #EAEAEA; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - margin: 20px 0px; - padding: 10px 30px; - background-color: #EEE; - border: 1px solid #CCC; -} - -div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fafafa; -} - -div.admonition p.admonition-title { - font-family: Georgia, serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: #fff; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.warning { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.danger { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.error { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.caution { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.attention { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.important { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.note { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.tip { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.hint { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.seealso { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.topic { - background-color: #EEE; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt, code { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -.hll { - background-color: #FFC; - margin: 0 -12px; - padding: 0 12px; - display: block; -} - -img.screenshot { -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.95em; -} - -tt.descname, code.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #EEE; - background: #FDFDFD; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.field-list p { - margin-bottom: 0.8em; -} - -/* Cloned from - * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 - */ -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -table.footnote td.label { - width: .1px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - /* Matches the 30px from the narrow-screen "li > ul" selector below */ - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #EEE; - padding: 7px 30px; - margin: 15px 0px; - line-height: 1.3em; -} - -div.viewcode-block:target { - background: #ffd; -} - -dl pre, blockquote pre, li pre { - margin-left: 0; - padding-left: 30px; -} - -tt, code { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, code.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fff; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -/* Don't put an underline on images */ -a.image-reference, a.image-reference:hover { - border-bottom: none; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt, a:hover code { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - li > ul { - /* Matches the 30px from the "ul, ol" selector above */ - margin-left: 30px; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: #fff; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: #FFF; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: #fff; - } - - div.sphinxsidebar a { - color: #AAA; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - - -/* misc. */ - -.revsys-inline { - display: none!important; -} - -/* Make nested-list/multi-paragraph items look better in Releases changelog - * pages. Without this, docutils' magical list fuckery causes inconsistent - * formatting between different release sub-lists. - */ -div#changelog > div.section > ul > li > p:only-child { - margin-bottom: 0; -} - -/* Hide fugly table cell borders in ..bibliography:: directive output */ -table.docutils.citation, table.docutils.citation td, table.docutils.citation th { - border: none; - /* Below needed in some edge cases; if not applied, bottom shadows appear */ - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - - -/* relbar */ - -.related { - line-height: 30px; - width: 100%; - font-size: 0.9rem; -} - -.related.top { - border-bottom: 1px solid #EEE; - margin-bottom: 20px; -} - -.related.bottom { - border-top: 1px solid #EEE; -} - -.related ul { - padding: 0; - margin: 0; - list-style: none; -} - -.related li { - display: inline; -} - -nav#rellinks { - float: right; -} - -nav#rellinks li+li:before { - content: "|"; -} - -nav#breadcrumbs li+li:before { - content: "\00BB"; -} - -/* Hide certain items when printing */ -@media print { - div.related { - display: none; - } -} \ No newline at end of file diff --git a/docs/_static/basic.css b/docs/_static/basic.css deleted file mode 100644 index 24a49f0..0000000 --- a/docs/_static/basic.css +++ /dev/null @@ -1,856 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 450px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a.brackets:before, -span.brackets > a:before{ - content: "["; -} - -a.brackets:after, -span.brackets > a:after { - content: "]"; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -dl.footnote > dt, -dl.citation > dt { - float: left; - margin-right: 0.5em; -} - -dl.footnote > dd, -dl.citation > dd { - margin-bottom: 0em; -} - -dl.footnote > dd:after, -dl.citation > dd:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dt:after { - content: ":"; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0.5em; - content: ":"; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css deleted file mode 100644 index 2a924f1..0000000 --- a/docs/_static/custom.css +++ /dev/null @@ -1 +0,0 @@ -/* This file intentionally left blank. */ diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js deleted file mode 100644 index 7d88f80..0000000 --- a/docs/_static/doctools.js +++ /dev/null @@ -1,316 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { - this.initOnKeyListeners(); - } - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated === 'undefined') - return string; - return (typeof translated === 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated === 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) === 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this === '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - $(document).keydown(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box, textarea, dropdown or button - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' - && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey - && !event.shiftKey) { - switch (event.keyCode) { - case 37: // left - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - case 39: // right - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js deleted file mode 100644 index 0030cfd..0000000 --- a/docs/_static/documentation_options.js +++ /dev/null @@ -1,12 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.1.2', - LANGUAGE: 'None', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false -}; \ No newline at end of file diff --git a/docs/_static/file.png b/docs/_static/file.png deleted file mode 100644 index a858a41..0000000 Binary files a/docs/_static/file.png and /dev/null differ diff --git a/docs/_static/graphviz.css b/docs/_static/graphviz.css deleted file mode 100644 index 8ab69e0..0000000 --- a/docs/_static/graphviz.css +++ /dev/null @@ -1,19 +0,0 @@ -/* - * graphviz.css - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- graphviz extension. - * - * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -img.graphviz { - border: 0; - max-width: 100%; -} - -object.graphviz { - max-width: 100%; -} diff --git a/docs/_static/jquery-3.5.1.js b/docs/_static/jquery-3.5.1.js deleted file mode 100644 index 5093733..0000000 --- a/docs/_static/jquery-3.5.1.js +++ /dev/null @@ -1,10872 +0,0 @@ -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Index

- -
- A - | B - | C - | D - | E - | F - | G - | I - | K - | L - | M - | N - | O - | P - | Q - | R - | S - | T - | U - | V - | W - | Z - -
-

A

- - - -
- -

B

- - -
- -

C

- - - -
- -

D

- - - -
- -

E

- - - -
- -

F

- - - -
- -

G

- - - -
- -

I

- - - -
- -

K

- - - -
- -

L

- - - -
- -

M

- - -
- -

N

- - - -
- -

O

- - - -
- -

P

- - - -
    -
  • - py2store.persisters.dropbox_w_dropbox - -
  • -
  • - py2store.persisters.googledrive_w_pydrive - -
  • -
  • - py2store.persisters.local_files - -
  • -
  • - py2store.persisters.new_s3 - -
  • -
  • - py2store.persisters.redis_w_redis - -
  • -
  • - py2store.persisters.s3_w_boto3 - -
  • -
  • - py2store.persisters.sql_w_sqlalchemy - -
  • -
  • - py2store.persisters.w_aiofile - -
  • -
  • - py2store.serializers - -
  • -
  • - py2store.serializers.pickled - -
  • -
  • - py2store.signatures - -
  • -
  • - py2store.slib - -
  • -
  • - py2store.slib.s_configparser - -
  • -
  • - py2store.slib.s_zipfile - -
  • -
  • - py2store.sources - -
  • -
  • - py2store.stores - -
  • -
  • - py2store.stores.dropbox_store - -
  • -
  • - py2store.stores.local_store - -
  • -
  • - py2store.stores.s3_store - -
  • -
  • - py2store.stores.sql_w_sqlalchemy - -
  • -
  • - py2store.test - -
  • -
  • - py2store.test.local_files_test - -
  • -
  • - py2store.test.quick_test - -
  • -
  • - py2store.test.scrap - -
  • -
  • - py2store.test.util - -
  • -
  • - py2store.trans - -
  • -
  • - py2store.util - -
  • -
  • - py2store.utils - -
  • -
  • - py2store.utils.affine_conversion - -
  • -
  • - py2store.utils.appendable - -
  • -
  • - py2store.utils.attr_dict - -
  • -
  • - py2store.utils.cache_descriptors - -
  • -
  • - py2store.utils.cumul_aggreg_write - -
  • -
  • - py2store.utils.explicit - -
  • -
  • - py2store.utils.glom - -
  • -
  • - py2store.utils.mappify - -
  • -
  • - py2store.utils.mg_selectors - -
  • -
  • - py2store.utils.mongoquery - -
  • -
  • - py2store.utils.signatures - -
  • -
  • - py2store.utils.sliceable - -
  • -
  • - py2store.utils.timeseries_caching - -
  • -
  • - py2store.utils.uri_utils - -
  • -
- -

Q

- - - -
- -

R

- - - -
- -

S

- - - -
- -

T

- - - -
- -

U

- - - -
- -

V

- - -
- -

W

- - -
- -

Z

- - - -
- - - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/how_to.html b/docs/how_to.html deleted file mode 100644 index aa61cd9..0000000 --- a/docs/how_to.html +++ /dev/null @@ -1,243 +0,0 @@ - - - - - - - - - A reader of multiple zip files — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

A reader of multiple zip files

-

You’ll find the main zip file handling stuff in module: py2store.slib.s_zipfile.

-

Let’s assume the most common case where your zips are in a folder (and possibly subfolders). -The three following objects should cover most of your cases:

-
from py2store.slib.s_zipfile import ZipFilesReader, FlatZipFilesReader, mk_flatzips_store
-
-
-

That’s in order of dependency ( mk_flatzips_store uses FlatZipFilesReader uses ZipFilesReader).

-
-

ZipFilesReader

-

Gives you a reader whose keys are full paths to zip files and values are ZipReader objects (by default, but customizable).

-
z = ZipFilesReader(zipdir)
-k, v = z.head()  # get a key and a value to see what this store is about
-print(f"{k=}")
-print(f"{type(v)=} {len(v)=}")
-
-
-
k='/Users/data/zips/some_zip_file.zip'
-type(v)=<class 'py2store.slib.s_zipfile.ZipReader'> len(v)=45
-
-
-

The length of v is the number of files in the some_zip_file.zip zip file.

-
-
-

FlatZipFilesReader

-

With ZipFilesReader you get access to zips of a folder, and contents of the zips through the values it provides. -But sometimes you want to have direct access to the contents of multiple zips. -FlatZipFilesReader will provide you with that.

-

It’s keys are (relative_zip_filepath, key_within_that_zip_file) pairs and it’s values are bytes of the zip content the key is pointing to.

-
z = FlatZipFilesReader(zipdir)
-k, v = z.head()  # get a key and a value to see what this store is about
-print(f"{k=}")
-print(f"{type(v)=} {len(v)=}")
-
-
-
k=('some_zip_file.zip', 'some_folder_in_zip/a_subfolder/a_file.xlsx')
-type(v)=<class 'bytes'> len(v)=19430710
-
-
-

The length here is the number of bytes that a_file.xlsx has.

-
-
-

mk_flatzips_store

-

Sometimes using (relative_zip_filepath, key_within_that_zip_file) as keys is not practical. -When you don’t like the key language, you can always change it. -You have trans.wrap_kvs to help with that, as well as several specialized tools like key_mappers.naming, etc.

-

In our current case, one practical key to use would be to just use the second element of the pair: The key of the particular zip file. -This isn’t a problem as long as they are all unique (amongst the multiple zip files). -The mk_flatzips_store does that work for you – checking for unicity and giving you a reader that uses such simpler keys:

-
z = mk_flatzips_store(zipdir)
-k, v = z.head()  # get a key and a value to see what this store is about
-print(f"{k=}")
-print(f"{type(v)=} {len(v)=}")
-
-
-
k='some_folder_in_zip/a_subfolder/a_file.xlsx'
-type(v)=<class 'bytes'> len(v)=1893
-
-
-
-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/http_docs.html b/docs/http_docs.html deleted file mode 100644 index 41e48a8..0000000 --- a/docs/http_docs.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - <no title> — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 83d129a..0000000 --- a/docs/index.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - - - Welcome to py2store’s documentation! — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Welcome to py2store’s documentation!

-
-

Contents:

- -
-
-
-

Indices and tables

- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/mockobjects.html b/docs/mockobjects.html deleted file mode 100644 index 6cb2b06..0000000 --- a/docs/mockobjects.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - Mock Objects — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Mock Objects

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store.html b/docs/module_docs/py2store.html deleted file mode 100644 index ec50a27..0000000 --- a/docs/module_docs/py2store.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - - py2store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store

-

Your portal to many Data Object Layer goodies

-
-
-py2store.ihead(store, n=1)[source]
-

Get the first item of an iterable, or a list of the first n items

-
- -
-
-py2store.kvhead(store, n=1)[source]
-

Get the first item of a kv store, or a list of the first n items

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/access.html b/docs/module_docs/py2store/access.html deleted file mode 100644 index 285b241..0000000 --- a/docs/module_docs/py2store/access.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - - - py2store.access — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.access

-

Utils to load stores from store specifications. -Includes the logic to allow configurations (and defaults) to be parametrized by external environmental -variables and files.

-

Every data-sourced problem has it’s problem-relevant stores. Once you get your stores right, along with the -right access credentials, indexing, serialization, caching, filtering etc. you’d like to be able to name, save -and/or share this specification, and easily get access to it later on.

-

Here are tools to help you out.

-

There are two main key-value stores: One for configurations the user wants to reuse, and the other for the user’s -desired defaults. Both have the same structure:

-
-
    -
  • first level key: Name of the resource (should be a valid python variable name)

  • -
  • The reminder is more or less free form (until the day we lay out some schemas for this)

  • -
-
-

The system will look for the specification of user_configs and user_defaults in a json file. -The filepath to this json file can specified in environment variables

-
-

PY2STORE_CONFIGS_JSON_FILEPATH and PY2STORE_DEFAULTS_JSON_FILEPATH

-
-

respectively. -By default, they are:

-
-

~/.py2store_configs.json and ~/.py2store_defaults.json

-
-

respectively.

-
-
-py2store.access.compose(*functions)[source]
-

Make a function that is the composition of the input functions

-
- -
-
-py2store.access.dflt_func_loader(f) → callable[source]
-

Loads and returns the function referenced by f, -which could be a callable or a DOTPATH_TO_MODULE.FUNC_NAME dotpath string to one, or a pipeline of these

-
- -
-
-py2store.access.dotpath_to_func(f: (<class 'str'>, <built-in function callable>)) → callable[source]
-

Loads and returns the function referenced by f, -which could be a callable or a DOTPATH_TO_MODULE.FUNC_NAME dotpath string to one.

-
- -
-
-py2store.access.dotpath_to_obj(dotpath)[source]
-

Loads and returns the object referenced by the string DOTPATH_TO_MODULE.OBJ_NAME

-
- -
-
-py2store.access.fakit(fak, func_loader=<function dflt_func_loader>)[source]
-

Execute a fak with given f, a, k and function loader.

-

Essentially returns func_loader(f)(*a, **k)

-
-
Parameters
-
    -
  • fak – A (f, a, k) specification. Could be a tuple or a dict (with ‘f’, ‘a’, ‘k’ keys). All but f are optional.

  • -
  • func_loader – A function returning a function. This is where you specify any validation of func specification f, -and/or how to get a callable from it.

  • -
-
-
-

Returns: A python object.

-
- -
-
-py2store.access.getenv(name, default=None)[source]
-

Like os.getenv, but removes a suffix r character if present (problem with some env var systems)

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/appendable.html b/docs/module_docs/py2store/appendable.html deleted file mode 100644 index de94250..0000000 --- a/docs/module_docs/py2store/appendable.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - py2store.appendable — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.appendable

-

Forwards to dol.appendable:

-
-
Tools to add append-functionality to key-val stores. The main function is

appendable_store_cls = add_append_functionality_to_store_cls(store_cls, item2kv, …)

-
-
-

You give it the store_cls you want to sub class, and a item -> (key, val) function, and you get a store (subclass) that -has a store.append(item) method. Also includes an extend method (that just called appends in a loop.

-

See add_append_functionality_to_store_cls docs for examples.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/base.html b/docs/module_docs/py2store/base.html deleted file mode 100644 index c0e5b15..0000000 --- a/docs/module_docs/py2store/base.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - py2store.base — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.base

-

Forwards to dol.base:

-

Base classes for making stores. -In the language of the collections.abc module, a store is a MutableMapping that is configured to work with a specific -representation of keys, serialization of objects (python values), and persistence of the serialized data.

-

That is, stores offer the same interface as a dict, but where the actual implementation of writes, reads, and listing -are configurable.

-

Consider the following example. You’re store is meant to store waveforms as wav files on a remote server. -Say waveforms are represented in python as a tuple (wf, sr), where wf is a list of numbers and sr is the sample -rate, an int). The __setitem__ method will specify how to store bytes on a remote server, but you’ll need to specify -how to SERIALIZE (wf, sr) to the bytes that constitute that wav file: _data_of_obj specifies that. -You might also want to read those wav files back into a python (wf, sr) tuple. The __getitem__ method will get -you those bytes from the server, but the store will need to know how to DESERIALIZE those bytes back into a python -object: _obj_of_data specifies that

-

Further, say you’re storing these .wav files in /some/folder/on/the/server/, but you don’t want the store to use -these as the keys. For one, it’s annoying to type and harder to read. But more importantly, it’s an irrelevant -implementation detail that shouldn’t be exposed. THe _id_of_key and _key_of_id pair are what allow you to -add this key interface layer.

-

These key converters object serialization methods default to the identity (i.e. they return the input as is). -This means that you don’t have to implement these as all, and can choose to implement these concerns within -the storage methods themselves.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/caching.html b/docs/module_docs/py2store/caching.html deleted file mode 100644 index 63b9ec5..0000000 --- a/docs/module_docs/py2store/caching.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.caching — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.caching

-

Forwards to dol.caching:

-

Tools to add caching layers to stores.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/core.html b/docs/module_docs/py2store/core.html deleted file mode 100644 index 13738db..0000000 --- a/docs/module_docs/py2store/core.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.core — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.core

-

Forwards to dol.core:

-

Core tools

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/dig.html b/docs/module_docs/py2store/dig.html deleted file mode 100644 index fef2dd4..0000000 --- a/docs/module_docs/py2store/dig.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.dig — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.dig

-

Forwards to dol.dig:

-

Layers introspection

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/errors.html b/docs/module_docs/py2store/errors.html deleted file mode 100644 index 550c564..0000000 --- a/docs/module_docs/py2store/errors.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.errors — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.errors

-

Forwards to dol.errors:

-

Error objects and utils

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples.html b/docs/module_docs/py2store/examples.html deleted file mode 100644 index 0564461..0000000 --- a/docs/module_docs/py2store/examples.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.examples — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples

-

modules demoing various uses of py2store

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples/code_navig.html b/docs/module_docs/py2store/examples/code_navig.html deleted file mode 100644 index 8fbda3b..0000000 --- a/docs/module_docs/py2store/examples/code_navig.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.examples.code_navig — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples.code_navig

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples/dropbox_w_urllib.html b/docs/module_docs/py2store/examples/dropbox_w_urllib.html deleted file mode 100644 index d8211cd..0000000 --- a/docs/module_docs/py2store/examples/dropbox_w_urllib.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - py2store.examples.dropbox_w_urllib — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples.dropbox_w_urllib

-

Dropbox read access with mapping interface – using only builtins

-
-
-class py2store.examples.dropbox_w_urllib.DropboxFileCopyReader(url, path=None)[source]
-
- -
-
-class py2store.examples.dropbox_w_urllib.DropboxFolderCopyReader(url, path='/tmp')[source]
-

Makes a full local copy of the folder (by default, to a local temp folder) and gives access to it.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples/kv_walking.html b/docs/module_docs/py2store/examples/kv_walking.html deleted file mode 100644 index ae5e3eb..0000000 --- a/docs/module_docs/py2store/examples/kv_walking.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - - py2store.examples.kv_walking — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples.kv_walking

-

walking through kv stores

-
-
-class py2store.examples.kv_walking.SrcReader(src, src_to_keys, key_to_obj)[source]
-
-
-update_keys_cache(keys)
-

Updates the _keys_cache by calling its {} method

-
- -
- -
-
-py2store.examples.kv_walking.conjunction(*args, **kwargs)[source]
-

` -will be equal to -` -func_1(*args, **kwargs) & … & func_n(*args, **kwargs) -``` -for all args, kwargs.

-
- -
-
-py2store.examples.kv_walking.kv_walk(v: collections.abc.Mapping, yield_func=<function asis>, walk_filt=<function val_is_mapping>, pkv_to_pv=<function tuple_keypath_and_val>, p=())[source]
-
-
Parameters
-
    -
  • v –

  • -
  • yield_func – (pp, k, vv) -> what ever you want the gen to yield

  • -
  • walk_filt – (p, k, vv) -> (bool) whether to explore the nested structure v further

  • -
  • pkv_to_pv – (p, k, v) -> (pp, vv) -where pp is a form of p + k (update of the path with the new node k) -and vv is the value that will be used by both walk_filt and yield_func

  • -
  • p – The path to v

  • -
-
-
-
>>> d = {'a': 1, 'b': {'c': 2, 'd': 3}}
->>> list(kv_walk(d))
-[(('a',), 'a', 1), (('b',), 'b', {'c': 2, 'd': 3}), (('b', 'c'), 'c', 2), (('b', 'd'), 'd', 3)]
->>> list(kv_walk(d, lambda p, k, v: '.'.join(p)))
-['a', 'b', 'b.c', 'b.d']
->>> list(kv_walk(d, lambda p, k, v: '.'.join(p)))
-['a', 'b', 'b.c', 'b.d']
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples/last_key_inserted.html b/docs/module_docs/py2store/examples/last_key_inserted.html deleted file mode 100644 index c8177d2..0000000 --- a/docs/module_docs/py2store/examples/last_key_inserted.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - py2store.examples.last_key_inserted — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples.last_key_inserted

-

showing how to add the knowledge of the ‘last key inserted’ to stores

-
-
-py2store.examples.last_key_inserted.remember_last_key_written_to(cls=None, *, only_if_new_key=False, name=None, same_name_as_class=False)[source]
-

A decorator to get a class that remembers the last key that was written to. -Note that this is the last key that THIS STORE wrote to, not the last key that was -written to the DB.

-

Some would say “it’s not thread-safe”, but that statement might be overkill. -See the code to see what you should or should not expect.

-
-
Parameters
-
    -
  • cls – The class you want to decorate (omit if you want to make a decorator factory instead)

  • -
  • only_if_new_key – If by “last written to” we mean “last created” -(i.e. only keep track if the key is NEW, not if we just updated the value)

  • -
  • name – The name you want the decorated class to have

  • -
  • same_name_as_class – If you want to use the name of the decorated class itself.

  • -
-
-
Returns
-

A decorated class (if the class was given), or a class decorator (if cls=None).

-
-
-
>>> def test(s):
-...     # test:
-...     s['hello'] = 'you'
-...     assert s._last_key_written_to == 'hello'
-...     s['goodbye'] = 'them'
-...     assert s._last_key_written_to == 'goodbye'
-...     s['hello'] = 'you'
-...     assert s._last_key_written_to == 'hello'
-...
->>>
->>> S = remember_last_key_written_to(dict)
->>> s = S()
->>> test(s)
->>>
->>> # Use as decorator factory
->>> @remember_last_key_written_to
-... class SS(dict):
-...     pass
-...
->>>
->>> ss = SS()
->>> test(ss)
->>>
->>> SSS = remember_last_key_written_to(dict, only_if_new_key=True)
->>> sss = SSS()
->>> assert sss._last_key_written_to is None
->>> sss['hi'] = 'there'
->>> sss._last_key_written_to
-'hi'
->>> sss[3] = .1415
->>> sss._last_key_written_to
-3
->>> sss['hi'] = 'this key already exists!'
->>> sss._last_key_written_to  # so this should still be 3
-3
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples/python_code_stats.html b/docs/module_docs/py2store/examples/python_code_stats.html deleted file mode 100644 index dff8bf1..0000000 --- a/docs/module_docs/py2store/examples/python_code_stats.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - py2store.examples.python_code_stats — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples.python_code_stats

-

Note: Moved to umpyre (pip install umpyre)

-

Get stats about packages. Your own, or other’s. -Things like…

-

# >>> import collections -# >>> modules_info_df(collections) -# lines empty_lines … num_of_functions num_of_classes -# collections.__init__ 1273 189 … 1 9 -# collections.abc 3 1 … 0 25 -# <BLANKLINE> -# [2 rows x 7 columns] -# >>> modules_info_df_stats(collections.abc) -# lines 1276.000000 -# empty_lines 190.000000 -# comment_lines 73.000000 -# docs_lines 133.000000 -# function_lines 138.000000 -# num_of_functions 1.000000 -# num_of_classes 34.000000 -# empty_lines_ratio 0.148903 -# comment_lines_ratio 0.057210 -# function_lines_ratio 0.108150 -# mean_lines_per_function 138.000000 -# dtype: float64 -# >>> stats_of([‘urllib’, ‘json’, ‘collections’]) -# urllib json collections -# empty_lines_ratio 0.157034 0.136818 0.148903 -# comment_lines_ratio 0.074142 0.038432 0.057210 -# function_lines_ratio 0.213907 0.449654 0.108150 -# mean_lines_per_function 13.463768 41.785714 138.000000 -# lines 4343.000000 1301.000000 1276.000000 -# empty_lines 682.000000 178.000000 190.000000 -# comment_lines 322.000000 50.000000 73.000000 -# docs_lines 425.000000 218.000000 133.000000 -# function_lines 929.000000 585.000000 138.000000 -# num_of_functions 69.000000 14.000000 1.000000 -# num_of_classes 55.000000 3.000000 34.000000

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/examples/write_caches.html b/docs/module_docs/py2store/examples/write_caches.html deleted file mode 100644 index 0758fb1..0000000 --- a/docs/module_docs/py2store/examples/write_caches.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - py2store.examples.write_caches — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.examples.write_caches

-

stores that implement various write caching algorithms

-
-
-py2store.examples.write_caches.timestamp_on_cache_and_concatenate_all_values()[source]
-

The cache timestamps (with system clock) every item on insertion (append) and uses the min timestamp as -a key for storage.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext.html b/docs/module_docs/py2store/ext.html deleted file mode 100644 index d3365a1..0000000 --- a/docs/module_docs/py2store/ext.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - py2store.ext — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext

-

py2store Extensions, Add-ons, etc. -We kept py2store purely dependency-less, using only built-ins for everything but storage system connectors.

-

That said, in order to provide the user with more power, and show him/her how py2store tools can be used to build -powerful data accessors, we provide specialized modules that do require more than builtins. These dependencies are -not listed in the setup.py module, but we wrap their imports with informative ImportError handlers.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/audio.html b/docs/module_docs/py2store/ext/audio.html deleted file mode 100644 index 48c5536..0000000 --- a/docs/module_docs/py2store/ext/audio.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.ext.audio — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.audio

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/dataframes.html b/docs/module_docs/py2store/ext/dataframes.html deleted file mode 100644 index 20cca33..0000000 --- a/docs/module_docs/py2store/ext/dataframes.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.ext.dataframes — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.dataframes

-

Data as pandas.DataFrame from various sources

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/docx.html b/docs/module_docs/py2store/ext/docx.html deleted file mode 100644 index ca1b8a1..0000000 --- a/docs/module_docs/py2store/ext/docx.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.ext.docx — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.docx

-

Simple access to docx (Word Doc) elements.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/github.html b/docs/module_docs/py2store/ext/github.html deleted file mode 100644 index 5bb672e..0000000 --- a/docs/module_docs/py2store/ext/github.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.ext.github — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.github

-

a data object layer for github

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/gitlab.html b/docs/module_docs/py2store/ext/gitlab.html deleted file mode 100644 index ba12a82..0000000 --- a/docs/module_docs/py2store/ext/gitlab.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - py2store.ext.gitlab — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.gitlab

-

Stores to talk to gitlab, using requests.

-

Example: -``` -ogl = GitLabAccessor(base_url=”http://…”, project_name=None)

-

print(ogl.get_project_names()) # prints all project names -ogl.set_project(“PROJECT_NAME”) # sets the project to “PROJECT_NAME” -print(

-
-

ogl.get_branch_names()

-
-

) # gets the branch names of current project (as set previously) -print(

-
-

ogl.get_branch(“master”)

-
-

) # gets a json of information about the master branch of current project. -```

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/hdf.html b/docs/module_docs/py2store/ext/hdf.html deleted file mode 100644 index bad8326..0000000 --- a/docs/module_docs/py2store/ext/hdf.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.ext.hdf — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.hdf

-

a data object layer for HDF files

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/kaggle.html b/docs/module_docs/py2store/ext/kaggle.html deleted file mode 100644 index e8a408a..0000000 --- a/docs/module_docs/py2store/ext/kaggle.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.ext.kaggle — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.kaggle

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/matlab.html b/docs/module_docs/py2store/ext/matlab.html deleted file mode 100644 index 3386eda..0000000 --- a/docs/module_docs/py2store/ext/matlab.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.ext.matlab — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.matlab

-

a data object layer for matlab

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/module_imports.html b/docs/module_docs/py2store/ext/module_imports.html deleted file mode 100644 index 7095d5f..0000000 --- a/docs/module_docs/py2store/ext/module_imports.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.ext.module_imports — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.module_imports

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/ext/wordnet.html b/docs/module_docs/py2store/ext/wordnet.html deleted file mode 100644 index d50aefd..0000000 --- a/docs/module_docs/py2store/ext/wordnet.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - py2store.ext.wordnet — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.ext.wordnet

-

The py2store wrapper to nltk.corpus.wordnet. Your no fuss gateway to (English) words.

-

The easiest way to get nltk.corpus.wordnet is

-

` -pip install nltk -` -in your terminal, and then in a python console:

-

` ->>> import nltk; nltk.download('wordnet')  # doctest: +SKIP -`

-

If you don’t like that way, [see here](https://www.nltk.org/install.html) for other ways to get wordnet.

-

The central construct of this module is the Synset (a set of synonyms that share a common meaning). -To see a few things you can do with Synsets, naked, [see here](https://www.nltk.org/howto/wordnet.html).

-

Here we put a py2store wrapper around this stuff.

-

What is WordNet? https://wordnet.princeton.edu/

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/filesys.html b/docs/module_docs/py2store/filesys.html deleted file mode 100644 index 9b34722..0000000 --- a/docs/module_docs/py2store/filesys.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.filesys — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.filesys

-

Forwards to dol.filesys:

-

File system access

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/key_mappers.html b/docs/module_docs/py2store/key_mappers.html deleted file mode 100644 index 7ec78cd..0000000 --- a/docs/module_docs/py2store/key_mappers.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.key_mappers — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.key_mappers

-

key mapping

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/key_mappers/naming.html b/docs/module_docs/py2store/key_mappers/naming.html deleted file mode 100644 index d59ad4b..0000000 --- a/docs/module_docs/py2store/key_mappers/naming.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.key_mappers.naming — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.key_mappers.naming

-

This module only forwards to py2store.naming, and is deprecated.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/key_mappers/paths.html b/docs/module_docs/py2store/key_mappers/paths.html deleted file mode 100644 index 43a3ca5..0000000 --- a/docs/module_docs/py2store/key_mappers/paths.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.key_mappers.paths — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.key_mappers.paths

-

Module that forwards to py2store.paths, kept for back-compatibility

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/key_mappers/str_utils.html b/docs/module_docs/py2store/key_mappers/str_utils.html deleted file mode 100644 index 8548db6..0000000 --- a/docs/module_docs/py2store/key_mappers/str_utils.html +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - - py2store.key_mappers.str_utils — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.key_mappers.str_utils

-

utils from strings

-
-
-py2store.key_mappers.str_utils.args_and_kwargs_indices(format_string)[source]
-

Get the sets of indices and names used in manual specification of format strings, or None, None if auto spec. -:param format_string: A format string (i.e. a string with {…} to mark parameter placement and formatting

-
-
Returns
-

None, None if format_string is an automatic specification -set_of_indices_used, set_of_fields_used if it is a manual specification

-
-
-
>>> format_string = '{0} (no 1) {2}, {see} this, {0} is a duplicate (appeared before) and {name} is string-named'
->>> assert args_and_kwargs_indices(format_string) == ({0, 2}, {'name', 'see'})
->>> format_string = 'This is a format string with only automatic field specification: {}, {}, {} etc.'
->>> assert args_and_kwargs_indices(format_string) == (set(), set())
-
-
-
- -
-
-py2store.key_mappers.str_utils.auto_field_format_str(format_str)[source]
-

Get an auto field version of the format_str

-
-
Parameters
-

format_str – A format string

-
-
Returns
-

A transformed format_str that has no names {inside} {formatting} {braces}.

-
-
-
>>> auto_field_format_str('R/{0}/{one}/{}/{two}/T')
-'R/{}/{}/{}/{}/T'
-
-
-
- -
-
-py2store.key_mappers.str_utils.compile_str_from_parsed(parsed)[source]
-

The (quasi-)inverse of string.Formatter.parse.

-
-
Parameters
-
    -
  • parsed – iterator of (literal_text, field_name, format_spec, conversion) tuples,

  • -
  • yield by string.Formatter.parse (as) –

  • -
-
-
Returns
-

A format string that would produce such a parsed input.

-
-
-
>>> s =  "ROOT/{}/{0!r}/{1!i:format}/hello{:0.02f}TAIL"
->>> assert compile_str_from_parsed(string.Formatter().parse(s)) == s
->>>
->>> # Or, if you want to see more details...
->>> parsed = list(string.Formatter().parse(s))
->>> for p in parsed:
-...     print(p)
-('ROOT/', '', '', None)
-('/', '0', '', 'r')
-('/', '1', 'format', 'i')
-('/hello', '', '0.02f', None)
-('TAIL', None, None, None)
->>> compile_str_from_parsed(parsed)
-'ROOT/{}/{0!r}/{1!i:format}/hello{:0.02f}TAIL'
-
-
-
- -
-
-py2store.key_mappers.str_utils.format_params_in_str_format(format_string)[source]
-

Get the “parameter” indices/names of the format_string

-
-
Parameters
-

format_string – A format string (i.e. a string with {…} to mark parameter placement and formatting

-
-
Returns
-

A list of parameter indices used in the format string, in the order they appear, with repetition. -Parameter indices could be integers, strings, or None (to denote “automatic field numbering”.

-
-
-
>>> format_string = '{0} (no 1) {2}, and {0} is a duplicate, {} is unnamed and {name} is string-named'
->>> format_params_in_str_format(format_string)
-[0, 2, 0, None, 'name']
-
-
-
- -
-
-py2store.key_mappers.str_utils.get_explicit_positions(parsed_str_format)[source]
-
>>> parsed = parse_str_format("all/{}/is/{2}/position/{except}{this}{0}")
->>> get_explicit_positions(parsed)
-{0, 2}
-
-
-
- -
-
-py2store.key_mappers.str_utils.is_automatic_format_params(format_params)[source]
-

Says if the format_params is from an automatic specification -See Also: is_manual_format_params and is_hybrid_format_params

-
- -
-
-py2store.key_mappers.str_utils.is_automatic_format_string(format_string)[source]
-

Says if the format_string is uses automatic specification -See Also: is_manual_format_params ->>> is_automatic_format_string(‘Manual: indices: {1} {2}, named: {named} {fields}’) -False ->>> is_automatic_format_string(‘Auto: only un-indexed and un-named: {} {}…’) -True ->>> is_automatic_format_string(‘Hybrid: at least a {}, and a {0} or a {name}’) -False ->>> is_manual_format_string(‘No formatting is both manual and automatic formatting!’) -True

-
- -
-
-py2store.key_mappers.str_utils.is_hybrid_format_params(format_params)[source]
-

Says if the format_params is from a hybrid of auto and manual. -Note: Hybrid specifications are considered non-valid and can’t be formatted with format_string.format(…). -Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). -See Also: is_manual_format_params and is_automatic_format_params

-
- -
-
-py2store.key_mappers.str_utils.is_hybrid_format_string(format_string)[source]
-

Says if the format_params is from a hybrid of auto and manual. -Note: Hybrid specifications are considered non-valid and can’t be formatted with format_string.format(…). -Yet, it can be useful for flexibility of expression (but will need to be resolved to be used).

-
>>> is_hybrid_format_string('Manual: indices: {1} {2}, named: {named} {fields}')
-False
->>> is_hybrid_format_string('Auto: only un-indexed and un-named: {} {}...')
-False
->>> is_hybrid_format_string('Hybrid: at least a {}, and a {0} or a {name}')
-True
->>> is_manual_format_string('No formatting is both manual and automatic formatting (so hybrid is both)!')
-True
-
-
-
- -
-
-py2store.key_mappers.str_utils.is_manual_format_params(format_params)[source]
-

Says if the format_params is from a manual specification -See Also: is_automatic_format_params

-
- -
-
-py2store.key_mappers.str_utils.is_manual_format_string(format_string)[source]
-

Says if the format_string uses a manual specification -See Also: is_automatic_format_string and ->>> is_manual_format_string(‘Manual: indices: {1} {2}, named: {named} {fields}’) -True ->>> is_manual_format_string(‘Auto: only un-indexed and un-named: {} {}…’) -False ->>> is_manual_format_string(‘Hybrid: at least a {}, and a {0} or a {name}’) -False ->>> is_manual_format_string(‘No formatting is both manual and automatic formatting!’) -True

-
- -
-
-py2store.key_mappers.str_utils.manual_field_format_str(format_str)[source]
-

Get an auto field version of the format_str

-
-
Parameters
-

format_str – A format string

-
-
Returns
-

A transformed format_str that has no names {inside} {formatting} {braces}.

-
-
-
>>> auto_field_format_str('R/{0}/{one}/{}/{two}/T')
-'R/{}/{}/{}/{}/T'
-
-
-
- -
-
-py2store.key_mappers.str_utils.n_format_params_in_str_format(format_string)[source]
-

The number of parameters

-
- -
-
-py2store.key_mappers.str_utils.name_fields_in_format_str(format_str, field_names=None)[source]
-

Get a manual field version of the format_str

-
-
Parameters
-
    -
  • format_str – A format string

  • -
  • names – An iterable that produces enough strings to fill all of format_str fields

  • -
-
-
Returns
-

A transformed format_str

-
-
-
>>> name_fields_in_format_str('R/{0}/{one}/{}/{two}/T')
-'R/{0}/{1}/{2}/{3}/T'
->>> # Note here that we use the field name to inject a field format as well
->>> name_fields_in_format_str('R/{foo}/{0}/{}/T', ['42', 'hi:03.0f', 'world'])
-'R/{42}/{hi:03.0f}/{world}/T'
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/key_mappers/tuples.html b/docs/module_docs/py2store/key_mappers/tuples.html deleted file mode 100644 index 6944313..0000000 --- a/docs/module_docs/py2store/key_mappers/tuples.html +++ /dev/null @@ -1,325 +0,0 @@ - - - - - - - - - py2store.key_mappers.tuples — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.key_mappers.tuples

-

Tools to map tuple-structured keys. -That is, converting from any of the following kinds of keys:

-
-
    -
  • tuples (or list-like)

  • -
  • dicts

  • -
  • formatted/templated strings

  • -
  • dsv (Delimiter-Separated Values)

  • -
-
-
-
-py2store.key_mappers.tuples.dsv_of_list(d, sep=',')[source]
-

Converting a list of strings to a dsv (delimiter-separated values) string.

-

Note that unlike most key mappers, there is no schema imposing size here. If you wish to impose a size -validation, do so externally (we suggest using a decorator for that).

-
-
Parameters
-
    -
  • d – A list of component strings

  • -
  • sep – The delimiter text used to separate a string into a list of component strings

  • -
-
-
Returns
-

The delimiter-separated values (dsv) string for the input tuple

-
-
-
>>> dsv_of_list(['a', 'brown', 'fox'], sep=' ')
-'a brown fox'
->>> dsv_of_list(('jumps', 'over'), sep='/')  # for filepaths (and see that tuple inputs work too!)
-'jumps/over'
->>> dsv_of_list(['Sat', 'Jan', '1', '1983'], sep=',')  # csv: the usual delimiter-separated values format
-'Sat,Jan,1,1983'
->>> dsv_of_list(['First', 'Last'], sep=':::')  # a longer delimiter
-'First:::Last'
->>> dsv_of_list(['singleton'], sep='@')  # when the list has only one element
-'singleton'
->>> dsv_of_list([], sep='@')  # when the list is empty
-''
-
-
-
- -
-
-py2store.key_mappers.tuples.list_of_dsv(d, sep=',')[source]
-

Converting a dsv (delimiter-separated values) string to the list of it’s components.

-
-
Parameters
-
    -
  • d – A (delimiter-separated values) string

  • -
  • sep – The delimiter text used to separate the string into a list of component strings

  • -
-
-
Returns
-

A list of component strings corresponding to the input delimiter-separated values (dsv) string

-
-
-
>>> list_of_dsv('a brown fox', sep=' ')
-['a', 'brown', 'fox']
->>> tuple(list_of_dsv('jumps/over', sep='/'))  # for filepaths
-('jumps', 'over')
->>> list_of_dsv('Sat,Jan,1,1983', sep=',')  # csv: the usual delimiter-separated values format
-['Sat', 'Jan', '1', '1983']
->>> list_of_dsv('First:::Last', sep=':::')  # a longer delimiter
-['First', 'Last']
->>> list_of_dsv('singleton', sep='@')  # when the list has only one element
-['singleton']
->>> list_of_dsv('', sep='@')  # when the string is empty
-[]
-
-
-
- -
-
-py2store.key_mappers.tuples.mk_obj_of_str(constructor)[source]
-

Make a function that transforms a string to an object. The factory making inverses of what mk_str_from_obj makes.

-
-
Parameters
-

constructor – The function (or class) that will be used to make objects from the **kwargs parsed out of the -string.

-
-
Returns
-

A function factory.

-
-
-
- -
-
-py2store.key_mappers.tuples.mk_str_of_obj(attrs)[source]
-

Make a function that transforms objects to strings, using specific attributes of object.

-
-
Parameters
-

attrs – Attributes that should be read off of the object to make the parameters of the string

-
-
Returns
-

A transformation function

-
-
-
>>> from dataclasses import dataclass
->>> @dataclass
-... class A:
-...     foo: int
-...     bar: str
->>> a = A(foo=0, bar='rin')
->>> a
-A(foo=0, bar='rin')
->>>
->>> str_from_obj = mk_str_of_obj(['foo', 'bar'])
->>> str_from_obj(a, 'ST{foo}/{bar}/G')
-'ST0/rin/G'
-
-
-
- -
-
-py2store.key_mappers.tuples.str_of_tuple(d, str_format)[source]
-

Convert tuple to str. -It’s just str_format.format(*d). Why even write such a function? -(1) To have a consistent interface for key conversions -(2) We want a KeyValidationError to occur here -:param d: tuple if params to str_format -:param str_format: Auto fields format string. If you have manual fields, consider auto_field_format_str to convert.

-
-
Returns
-

parametrized string

-
-
-
>>> str_of_tuple(('hello', 'world'), "Well, {} dear {}!")
-'Well, hello dear world!'
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/misc.html b/docs/module_docs/py2store/misc.html deleted file mode 100644 index 38637f8..0000000 --- a/docs/module_docs/py2store/misc.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - - - - py2store.misc — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.misc

-

Functions to read from and write to misc sources

-
-
-class py2store.misc.MiscGetter(store=<py2store.persisters.local_files.PathFormatPersister object>, incoming_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function <lambda>>, '.gz': <function decompress>, '.gzip': <function decompress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>, '.zip': <class 'py2store.slib.s_zipfile.FilesOfZip'>}, dflt_incoming_val_trans=<function identity_method>, func_key=<function MiscGetter.<lambda>>)[source]
-

An object to write (and only write) to a store (default local files) with automatic deserialization -according to a property of the key (default: file extension).

-
>>> from py2store.misc import get_obj, misc_objs_get
->>> import os
->>> import json
->>>
->>> pjoin = lambda *p: os.path.join(os.path.expanduser('~'), *p)
->>> path = pjoin('tmp.json')
->>> d = {'a': {'b': {'c': [1, 2, 3]}}}
->>> json.dump(d, open(path, 'w'))  # putting a json file there, the normal way, so we can use it later
->>>
->>> k = path
->>> t = get_obj(k)  # if you'd like to use a function
->>> assert t == d
->>> tt = misc_objs_get[k]  # if you'd like to use an object (note: can get, but nothing else (no list, set, del, etc))
->>> assert tt == d
->>> t
-{'a': {'b': {'c': [1, 2, 3]}}}
-
-
-
- -
-
-class py2store.misc.MiscGetterAndSetter(store=<py2store.persisters.local_files.PathFormatPersister object>, incoming_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function <lambda>>, '.gz': <function decompress>, '.gzip': <function decompress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>, '.zip': <class 'py2store.slib.s_zipfile.FilesOfZip'>}, outgoing_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function csv_fileobj>, '.gz': <function compress>, '.gzip': <function compress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>}, dflt_incoming_val_trans=<function identity_method>, func_key=<function MiscGetterAndSetter.<lambda>>)[source]
-

An object to read and write (and nothing else) to a store (default local) with automatic (de)serialization -according to a property of the key (default: file extension).

-
>>> from py2store.misc import set_obj, misc_objs  # the function and the object
->>> import json
->>> import os
->>>
->>> pjoin = lambda *p: os.path.join(os.path.expanduser('~'), *p)
->>>
->>> d = {'a': {'b': {'c': [1, 2, 3]}}}
->>> misc_objs[pjoin('tmp.json')] = d
->>> filepath = os.path.expanduser('~/tmp.json')
->>> assert misc_objs[filepath] == d  # yep, it's there, and can be retrieved
->>> assert json.load(open(filepath)) == d  # in case you don't believe it's an actual json file
->>>
->>> # using pickle
->>> misc_objs[pjoin('tmp.pkl')] = d
->>> assert misc_objs[pjoin('tmp.pkl')] == d
->>>
->>> # using txt
->>> misc_objs[pjoin('tmp.txt')] = 'hello world!'
->>> assert misc_objs[pjoin('tmp.txt')] == 'hello world!'
->>>
->>> # using csv
->>> misc_objs[pjoin('tmp.csv')] = [[1,2,3], ['a','b','c']]
->>> assert misc_objs[pjoin('tmp.csv')] == [['1','2','3'], ['a','b','c']]  # yeah, well, not numbers, but you deal with it
->>>
->>> # using bin
-... misc_objs[pjoin('tmp.bin')] = b'let us pretend these are bytes of an audio waveform'
->>> assert misc_objs[pjoin('tmp.bin')] == b'let us pretend these are bytes of an audio waveform'
-
-
-
- -
-
-class py2store.misc.MiscReaderMixin(incoming_val_trans_for_key=None, dflt_incoming_val_trans=None, func_key=None)[source]
-

Mixin to transform incoming vals according to the key their under. -Warning: If used as a subclass, this mixin should (in general) be placed before the store

-
>>> # make a reader that will wrap a dict
->>> class MiscReader(MiscReaderMixin, dict):
-...     def __init__(self, d,
-...                         incoming_val_trans_for_key=None,
-...                         dflt_incoming_val_trans=None,
-...                         func_key=None):
-...         dict.__init__(self, d)
-...         MiscReaderMixin.__init__(self, incoming_val_trans_for_key, dflt_incoming_val_trans, func_key)
-...
->>>
->>> incoming_val_trans_for_key = dict(
-...     MiscReaderMixin._incoming_val_trans_for_key,  # take the existing defaults...
-...     **{'.bin': lambda v: [ord(x) for x in v.decode()], # ... override how to handle the .bin extension
-...      '.reverse_this': lambda v: v[::-1]  # add a new extension (and how to handle it)
-...     })
->>>
->>> import pickle
->>> d = {
-...     'a.bin': b'abc123',
-...     'a.reverse_this': b'abc123',
-...     'a.csv': b'event,year\n Magna Carta,1215\n Guido,1956',
-...     'a.txt': b'this is not a text',
-...     'a.pkl': pickle.dumps(['text', [str, map], {'a list': [1, 2, 3]}]),
-...     'a.json': '{"str": "field", "int": 42, "float": 3.14, "array": [1, 2], "nested": {"a": 1, "b": 2}}',
-... }
->>>
->>> s = MiscReader(d=d, incoming_val_trans_for_key=incoming_val_trans_for_key)
->>> list(s)
-['a.bin', 'a.reverse_this', 'a.csv', 'a.txt', 'a.pkl', 'a.json']
->>> s['a.bin']
-[97, 98, 99, 49, 50, 51]
->>> s['a.reverse_this']
-b'321cba'
->>> s['a.csv']
-[['event', 'year'], [' Magna Carta', '1215'], [' Guido', '1956']]
->>> s['a.pkl']
-['text', [<class 'str'>, <class 'map'>], {'a list': [1, 2, 3]}]
->>> s['a.json']
-{'str': 'field', 'int': 42, 'float': 3.14, 'array': [1, 2], 'nested': {'a': 1, 'b': 2}}
-
-
-
- -
-
-class py2store.misc.MiscStoreMixin(incoming_val_trans_for_key=None, outgoing_val_trans_for_key=None, dflt_incoming_val_trans=None, dflt_outgoing_val_trans=None, func_key=None)[source]
-

Mixin to transform incoming and outgoing vals according to the key their under. -Warning: If used as a subclass, this mixin should (in general) be placed before the store

-

See also: preset and postget args from wrap_kvs decorator from py2store.trans.

-
>>> # Make a class to wrap a dict with a layer that transforms written and read values
->>> class MiscStore(MiscStoreMixin, dict):
-...     def __init__(self, d,
-...                         incoming_val_trans_for_key=None, outgoing_val_trans_for_key=None,
-...                         dflt_incoming_val_trans=None, dflt_outgoing_val_trans=None,
-...                         func_key=None):
-...         dict.__init__(self, d)
-...         MiscStoreMixin.__init__(self, incoming_val_trans_for_key, outgoing_val_trans_for_key,
-...                                 dflt_incoming_val_trans, dflt_outgoing_val_trans, func_key)
-...
->>>
->>> outgoing_val_trans_for_key = dict(
-...     MiscStoreMixin._outgoing_val_trans_for_key,  # take the existing defaults...
-...     **{'.bin': lambda v: ''.join([chr(x) for x in v]).encode(), # ... override how to handle the .bin extension
-...        '.reverse_this': lambda v: v[::-1]  # add a new extension (and how to handle it)
-...     })
->>> ss = MiscStore(d={},  # store starts empty
-...                incoming_val_trans_for_key={},  # overriding incoming trans so we can see the raw data later
-...                outgoing_val_trans_for_key=outgoing_val_trans_for_key)
-...
->>> # here's what we're going to write in the store
->>> data_to_write = {
-...      'a.bin': [97, 98, 99, 49, 50, 51],
-...      'a.reverse_this': b'321cba',
-...      'a.csv': [['event', 'year'], [' Magna Carta', '1215'], [' Guido', '1956']],
-...      'a.txt': 'this is not a text',
-...      'a.pkl': ['text', [str, map], {'a list': [1, 2, 3]}],
-...      'a.json': {'str': 'field', 'int': 42, 'float': 3.14, 'array': [1, 2], 'nested': {'a': 1, 'b': 2}}}
->>> # write this data in our store
->>> for k, v in data_to_write.items():
-...     ss[k] = v
->>> list(ss)
-['a.bin', 'a.reverse_this', 'a.csv', 'a.txt', 'a.pkl', 'a.json']
->>> # Looking at the contents (what was actually stored/written)
->>> for k, v in ss.items():
-...     if k != 'a.pkl':
-...         print(f"{k}: {v}")
-...     else:  # need to verify pickle data differently, since printing contents is problematic in doctest
-...         assert pickle.loads(v) == data_to_write['a.pkl']
-a.bin: b'abc123'
-a.reverse_this: b'abc123'
-a.csv: b'event,year\r\n Magna Carta,1215\r\n Guido,1956\r\n'
-a.txt: b'this is not a text'
-a.json: b'{"str": "field", "int": 42, "float": 3.14, "array": [1, 2], "nested": {"a": 1, "b": 2}}'
-
-
-
- -
-
-py2store.misc.get_obj(k, store=<py2store.persisters.local_files.PathFormatPersister object>, incoming_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function <lambda>>, '.gz': <function decompress>, '.gzip': <function decompress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>, '.zip': <class 'py2store.slib.s_zipfile.FilesOfZip'>}, dflt_incoming_val_trans=<function identity_method>, func_key=<function <lambda>>)[source]
-

A quick way to get an object, with default… everything (but the key, you know, a clue of what you want)

-
- -
-
-py2store.misc.set_obj(k, v, store=<py2store.persisters.local_files.PathFormatPersister object>, outgoing_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function csv_fileobj>, '.gz': <function compress>, '.gzip': <function compress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>}, func_key=<function <lambda>>)[source]
-

A quick way to get an object, with default… everything (but the key, you know, a clue of what you want)

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/mixins.html b/docs/module_docs/py2store/mixins.html deleted file mode 100644 index dd159db..0000000 --- a/docs/module_docs/py2store/mixins.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.mixins — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.mixins

-

Forwards to dol.mixins:

-

Mixins

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/my.html b/docs/module_docs/py2store/my.html deleted file mode 100644 index bce837e..0000000 --- a/docs/module_docs/py2store/my.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.my — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.my

-

functionalities meant to be configurable

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/my/grabbers.html b/docs/module_docs/py2store/my/grabbers.html deleted file mode 100644 index c5688ee..0000000 --- a/docs/module_docs/py2store/my/grabbers.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.my.grabbers — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.my.grabbers

-

define stores (and functions) so they give you data as you want it, depending on the extension

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/naming.html b/docs/module_docs/py2store/naming.html deleted file mode 100644 index f89705f..0000000 --- a/docs/module_docs/py2store/naming.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.naming — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.naming

-

Forwards to dol.naming:

-

This module is about generating, validating, and operating on (parametrized) fields (i.e. stings, e.g. paths).

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/parse_format.html b/docs/module_docs/py2store/parse_format.html deleted file mode 100644 index d57c8df..0000000 --- a/docs/module_docs/py2store/parse_format.html +++ /dev/null @@ -1,757 +0,0 @@ - - - - - - - - - py2store.parse_format — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.parse_format

-

Modified from https://github.com/r1chardj0n3s/parse

-

Parse strings using a specification based on the Python format() syntax.

-
-

parse() is the opposite of format()

-
-

From there it’s a simple thing to parse a string:

-
>>> parse("It's {}, I love it!", "It's spam, I love it!")
-<Result ('spam',) {}>
->>> _[0]
-'spam'
-
-
-

Or to search a string for some pattern:

-
>>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n')
-<Result (42,) {}>
-
-
-

Or find all the occurrences of some pattern in a string:

-
>>> ''.join(r.fixed[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>"))
-'the bold text'
-
-
-

If you’re going to use the same pattern to match lots of strings you can -compile it once:

-
>>> p = compile("It's {}, I love it!")
->>> print(p)
-<Parser "It's {}, I love it!">
->>> p.parse("It's spam, I love it!")
-<Result ('spam',) {}>
-
-
-

(“compile” is not exported for import * usage as it would override the -built-in compile() function)

-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True:

-
>>> parse('SPAM', 'spam', case_sensitive=True) is None
-True
-
-
-
-

Format Syntax

-

A basic version of the Format String Syntax is supported with anonymous -(fixed-position), named and formatted fields:

-
{[field name]:[format spec]}
-
-
-

Field names must be a valid Python identifiers, including dotted names; -element indexes imply dictionaries (see below for example).

-

Numbered fields are also not supported: the result of parsing will include -the parsed fields in the order they are parsed.

-

The conversion of fields to types other than strings is done based on the -type in the format specification, which mirrors the format() behaviour. -There are no “!” field conversions like format() has.

-

Some simple parse() format string examples:

-
>>> parse("Bring me a {}", "Bring me a shrubbery")
-<Result ('shrubbery',) {}>
->>> r = parse("The {} who say {}", "The knights who say Ni!")
->>> print(r)
-<Result ('knights', 'Ni!') {}>
->>> print(r.fixed)
-('knights', 'Ni!')
->>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade")
->>> print(r)
-<Result () {'item': 'hand grenade'}>
->>> print(r.named)
-{'item': 'hand grenade'}
->>> print(r['item'])
-hand grenade
-
-
-

Dotted names and indexes are possible though the application must make -additional sense of the result:

-
>>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
->>> print(r)
-<Result () {'food.type': 'spam'}>
->>> print(r.named)
-{'food.type': 'spam'}
->>> print(r['food.type'])
-spam
->>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!")
->>> print(r)
-<Result () {'quest': {'name': 'to seek the holy grail!'}}>
->>> print(r['quest'])
-{'name': 'to seek the holy grail!'}
->>> print(r['quest']['name'])
-to seek the holy grail!
-
-
-

If the text you’re matching has braces in it you can match those by including -a double-brace {{ or }} in your format string, just like format() does.

-
-
-

Format Specification

-

Most often a straight format-less {} will suffice where a more complex -format specification might have been used.

-

Most of format()’s Format Specification Mini-Language is supported:

-
-

[[fill]align][0][width][.precision][type]

-
-

The differences between parse() and format() are:

-
    -
  • The align operators will cause spaces (or specified fill character) to be -stripped from the parsed value. The width is not enforced; it just indicates -there may be whitespace or “0”s to strip.

  • -
  • Numeric parsing will automatically handle a “0b”, “0o” or “0x” prefix. -That is, the “#” format character is handled automatically by d, b, o -and x formats. For “d” any will be accepted, but for the others the correct -prefix must be present if at all.

  • -
  • Numeric sign is handled automatically.

  • -
  • The thousands separator is handled automatically if the “n” type is used.

  • -
  • The types supported are a slightly different mix to the format() types. Some -format() types come directly over: “d”, “n”, “%”, “f”, “e”, “b”, “o” and “x”. -In addition some regular expression character group types “D”, “w”, “W”, “s” -and “S” are also available.

  • -
  • The “e” and “g” types are case-insensitive so there is not need for -the “E” or “G” types.

  • -
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Type

Characters Matched

Output

w

Letters and underscore

str

W

Non-letter and underscore

str

s

Whitespace

str

S

Non-whitespace

str

d

Digits (effectively integer numbers)

int

D

Non-digit

str

n

Numbers with thousands separators (, or .)

int

%

Percentage (converted to value/100.0)

float

f

Fixed-point numbers

float

F

Decimal numbers

Decimal

e

Floating-point numbers with exponent -e.g. 1.1e-10, NAN (all case insensitive)

float

g

General number format (either d, f or e)

float

b

Binary numbers

int

o

Octal numbers

int

x

Hexadecimal numbers (lower and upper case)

int

ti

ISO 8601 format date/time -e.g. 1972-01-20T10:21:36Z (“T” and “Z” -optional)

datetime

te

RFC2822 e-mail format date/time -e.g. Mon, 20 Jan 1972 10:21:36 +1000

datetime

tg

Global (day/month) format date/time -e.g. 20/1/1972 10:21:36 AM +1:00

datetime

ta

US (month/day) format date/time -e.g. 1/20/1972 10:21:36 PM +10:30

datetime

tc

ctime() format date/time -e.g. Sun Sep 16 01:03:52 1973

datetime

th

HTTP log format date/time -e.g. 21/Nov/2011:00:07:11 +0000

datetime

ts

Linux system log format date/time -e.g. Nov 9 03:37:44

datetime

tt

Time -e.g. 10:21:36 PM -5:30

time

-

Some examples of typed parsing with None returned if the typing -does not match:

-
>>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...')
-<Result (3, 'weapons') {}>
->>> parse('Our {:d} {:w} are...', 'Our three weapons are...')
->>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM')
-<Result (datetime.datetime(2011, 2, 1, 23, 0),) {}>
-
-
-

And messing about with alignment:

-
>>> parse('with {:>} herring', 'with     a herring')
-<Result ('a',) {}>
->>> parse('spam {:^} spam', 'spam    lovely     spam')
-<Result ('lovely',) {}>
-
-
-

Note that the “center” alignment does not test to make sure the value is -centered - it just strips leading and trailing whitespace.

-

Width and precision may be used to restrict the size of matched text -from the input. Width specifies a minimum size and precision specifies -a maximum. For example:

-
>>> parse('{:.2}{:.2}', 'look')           # specifying precision
-<Result ('lo', 'ok') {}>
->>> parse('{:4}{:4}', 'look at that')     # specifying width
-<Result ('look', 'at that') {}>
->>> parse('{:4}{:.4}', 'look at that')    # specifying both
-<Result ('look at ', 'that') {}>
->>> parse('{:2d}{:2d}', '0440')           # parsing two contiguous numbers
-<Result (4, 40) {}>
-
-
-

Some notes for the date and time types:

-
    -
  • the presence of the time part is optional (including ISO 8601, starting -at the “T”). A full datetime object will always be returned; the time -will be set to 00:00:00. You may also specify a time without seconds.

  • -
  • when a seconds amount is present in the input fractions will be parsed -to give microseconds.

  • -
  • except in ISO 8601 the day and month digits may be 0-padded.

  • -
  • the date separator for the tg and ta formats may be “-” or “/”.

  • -
  • named months (abbreviations or full names) may be used in the ta and tg -formats in place of numeric months.

  • -
  • as per RFC 2822 the e-mail format may omit the day (and comma), and the -seconds but nothing else.

  • -
  • hours greater than 12 will be happily accepted.

  • -
  • the AM/PM are optional, and if PM is found then 12 hours will be added -to the datetime object’s hours amount - even if the hour is greater -than 12 (for consistency.)

  • -
  • in ISO 8601 the “Z” (UTC) timezone part may be a numeric offset

  • -
  • timezones are specified as “+HH:MM” or “-HH:MM”. The hour may be one or two -digits (0-padded is OK.) Also, the “:” is optional.

  • -
  • the timezone is optional in all except the e-mail format (it defaults to -UTC.)

  • -
  • named timezones are not handled yet.

  • -
-

Note: attempting to match too many datetime fields in a single parse() will -currently result in a resource allocation issue. A TooManyFields exception -will be raised in this instance. The current limit is about 15. It is hoped -that this limit will be removed one day.

-
-
-

Result and Match Objects

-

The result of a parse() and search() operation is either None (no match), a -Result instance or a Match instance if evaluate_result is False.

-

The Result instance has three attributes:

-
-
fixed

A tuple of the fixed-position, anonymous fields extracted from the input.

-
-
named

A dictionary of the named fields extracted from the input.

-
-
spans

A dictionary mapping the names and fixed position indices matched to a -2-tuple slice range of where the match occurred in the input. -The span does not include any stripped padding (alignment or width).

-
-
-

The Match instance has one method:

-
-
evaluate_result()

Generates and returns a Result instance for this Match object.

-
-
-
-
-

Custom Type Conversions

-

If you wish to have matched fields automatically converted to your own type you -may pass in a dictionary of type conversion information to parse() and -compile().

-

The converter will be passed the field string matched. Whatever it returns -will be substituted in the Result instance for that field.

-

Your custom type conversions may override the builtin types if you supply one -with the same identifier.

-
>>> def shouty(string):
-...    return string.upper()
-...
->>> parse('{:shouty} world', 'hello world', dict(shouty=shouty))
-<Result ('HELLO',) {}>
-
-
-

If the type converter has the optional pattern attribute, it is used as -regular expression for better pattern matching (instead of the default one).

-
>>> def parse_number(text):
-...    return int(text)
->>> parse_number.pattern = r'\d+'
->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
-<Result () {'number': 42}>
->>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number))
->>> assert _ is None, "MISMATCH"
-
-
-

You can also use the with_pattern(pattern) decorator to add this -information to a type converter function:

-
>>> @with_pattern(r'\d+')
-... def parse_number(text):
-...    return int(text)
->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
-<Result () {'number': 42}>
-
-
-

A more complete example of a custom type might be:

-
>>> yesno_mapping = {
-...     "yes":  True,   "no":    False,
-...     "on":   True,   "off":   False,
-...     "true": True,   "false": False,
-... }
->>> @with_pattern(r"|".join(yesno_mapping))
-... def parse_yesno(text):
-...     return yesno_mapping[text.lower()]
-
-
-

If the type converter pattern uses regex-grouping (with parenthesis), -you should indicate this by using the optional regex_group_count parameter -in the with_pattern() decorator:

-
>>> @with_pattern(r'((\d+))', regex_group_count=2)
-... def parse_number2(text):
-...    return int(text)
->>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
-<Result (42, 43) {}>
-
-
-

Otherwise, this may cause parsing problems with unnamed/fixed parameters.

-
-
-

Potential Gotchas

-

parse() will always match the shortest text necessary (from left to right) -to fulfil the parse pattern, so for example:

-
>>> pattern = '{dir1}/{dir2}'
->>> data = 'root/parent/subdir'
->>> sorted(parse(pattern, data).named.items())
-[('dir1', 'root'), ('dir2', 'parent/subdir')]
-
-
-

So, even though {‘dir1’: ‘root/parent’, ‘dir2’: ‘subdir’} would also fit -the pattern, the actual match represents the shortest successful match for -dir1.

-
-

Version history (in brief):

-
    -
  • 1.9.0 We now honor precision and width specifiers when parsing numbers -and strings, allowing parsing of concatenated elements of fixed width -(thanks Julia Signell)

  • -
  • 1.8.4 Add LICENSE file at request of packagers. -Correct handling of AM/PM to follow most common interpretation. -Correct parsing of hexadecimal that looks like a binary prefix. -Add ability to parse case sensitively. -Add parsing of numbers to Decimal with “F” (thanks John Vandenberg)

  • -
  • 1.8.3 Add regex_group_count to with_pattern() decorator to support -user-defined types that contain brackets/parenthesis (thanks Jens Engel)

  • -
  • 1.8.2 add documentation for including braces in format string

  • -
  • 1.8.1 ensure bare hexadecimal digits are not matched

  • -
  • 1.8.0 support manual control over result evaluation (thanks Timo Furrer)

  • -
  • 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow -more than 100 re groups in Python 3.5+ (thanks David King)

  • -
  • 1.6.6 parse Linux system log dates (thanks Alex Cowan)

  • -
  • 1.6.5 handle precision in float format (thanks Levi Kilcher)

  • -
  • 1.6.4 handle pipe “|” characters in parse string (thanks Martijn Pieters)

  • -
  • 1.6.3 handle repeated instances of named fields, fix bug in PM time -overflow

  • -
  • 1.6.2 fix logging to use local, not root logger (thanks Necku)

  • -
  • 1.6.1 be more flexible regarding matched ISO datetimes and timezones in -general, fix bug in timezones without “:” and improve docs

  • -
  • 1.6.0 add support for optional pattern attribute in user-defined types -(thanks Jens Engel)

  • -
  • 1.5.3 fix handling of question marks

  • -
  • 1.5.2 fix type conversion error with dotted names (thanks Sebastian Thiel)

  • -
  • 1.5.1 implement handling of named datetime fields

  • -
  • 1.5 add handling of dotted field names (thanks Sebastian Thiel)

  • -
  • 1.4.1 fix parsing of “0” in int conversion (thanks James Rowe)

  • -
  • 1.4 add __getitem__ convenience access on Result.

  • -
  • 1.3.3 fix Python 2.5 setup.py issue.

  • -
  • 1.3.2 fix Python 3.2 setup.py issue.

  • -
  • 1.3.1 fix a couple of Python 3.2 compatibility issues.

  • -
  • 1.3 added search() and findall(); removed compile() from import * -export as it overwrites builtin.

  • -
  • 1.2 added ability for custom and override type conversions to be -provided; some cleanup

  • -
  • 1.1.9 to keep things simpler number sign is handled automatically; -significant robustification in the face of edge-case input.

  • -
  • 1.1.8 allow “d” fields to have number base “0x” etc. prefixes; -fix up some field type interactions after stress-testing the parser; -implement “%” type.

  • -
  • 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported).

  • -
  • 1.1.6 add “e” and “g” field types; removed redundant “h” and “X”; -removed need for explicit “#”.

  • -
  • 1.1.5 accept textual dates in more places; Result now holds match span -positions.

  • -
  • 1.1.4 fixes to some int type conversion; implemented “=” alignment; added -date/time parsing with a variety of formats handled.

  • -
  • 1.1.3 type conversion is automatic based on specified field types. Also added -“f” and “n” types.

  • -
  • 1.1.2 refactored, added compile() and limited from parse import *

  • -
  • 1.1.1 documentation improvements

  • -
  • 1.1.0 implemented more of the Format Specification Mini-Language -and removed the restriction on mixing fixed-position and named fields

  • -
  • 1.0.0 initial release

  • -
-

This code is copyright 2012-2017 Richard Jones <richard@python.org> -See the end of the source file for the license of use.

-
-
-py2store.parse_format.findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, case_sensitive=False)[source]
-

Search “string” for all occurrences of “format”.

-

You will be returned an iterator that holds Result instances -for each format match found.

-

Optionally start the search at “pos” character index and limit the search -to a maximum index of endpos - equivalent to search(string[:endpos]).

-

If evaluate_result is True each returned Result instance has two attributes:

-
-

.fixed - tuple of fixed-position values from the string -.named - dict of named values from the string

-
-

If evaluate_result is False each returned value is a Match instance with one method:

-
-
-
.evaluate_result() - This will return a Result instance like you would get

with evaluate_result set to True

-
-
-
-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True.

-

If the format is invalid a ValueError will be raised.

-

See the module documentation for the use of “extra_types”.

-
- -
-
-py2store.parse_format.parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False)[source]
-

Using “format” attempt to pull values from “string”.

-

The format must match the string contents exactly. If the value -you’re looking for is instead just a part of the string use -search().

-

If evaluate_result is True the return value will be an Result instance with two attributes:

-
-

.fixed - tuple of fixed-position values from the string -.named - dict of named values from the string

-
-

If evaluate_result is False the return value will be a Match instance with one method:

-
-
-
.evaluate_result() - This will return a Result instance like you would get

with evaluate_result set to True

-
-
-
-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True.

-

If the format is invalid a ValueError will be raised.

-

See the module documentation for the use of “extra_types”.

-

In the case there is no match parse() will return None.

-
- -
-
-py2store.parse_format.search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, case_sensitive=False)[source]
-

Search “string” for the first occurrence of “format”.

-

The format may occur anywhere within the string. If -instead you wish for the format to exactly match the string -use parse().

-

Optionally start the search at “pos” character index and limit the search -to a maximum index of endpos - equivalent to search(string[:endpos]).

-

If evaluate_result is True the return value will be an Result instance with two attributes:

-
-

.fixed - tuple of fixed-position values from the string -.named - dict of named values from the string

-
-

If evaluate_result is False the return value will be a Match instance with one method:

-
-
-
.evaluate_result() - This will return a Result instance like you would get

with evaluate_result set to True

-
-
-
-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True.

-

If the format is invalid a ValueError will be raised.

-

See the module documentation for the use of “extra_types”.

-

In the case there is no match parse() will return None.

-
- -
-
-py2store.parse_format.with_pattern(pattern, regex_group_count=None)[source]
-

Attach a regular expression pattern matcher to a custom type converter -function.

-

This annotates the type converter with the pattern attribute.

-

Example

-
>>> @with_pattern(r"\d+")
-... def parse_number(text):
-...     return int(text)
-
-
-

is equivalent to:

-
>>> def parse_number(text):
-...     return int(text)
->>> parse_number.pattern = r"\d+"
-
-
-
-
Parameters
-
    -
  • pattern – regular expression pattern (as text)

  • -
  • regex_group_count – Indicates how many regex-groups are in pattern.

  • -
-
-
Returns
-

wrapped function

-
-
-
- -
-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/paths.html b/docs/module_docs/py2store/paths.html deleted file mode 100644 index b613b45..0000000 --- a/docs/module_docs/py2store/paths.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.paths — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.paths

-

Forwards to dol.paths:

-

Module for path (and path-like) object manipulation

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters.html b/docs/module_docs/py2store/persisters.html deleted file mode 100644 index 18d88bd..0000000 --- a/docs/module_docs/py2store/persisters.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters

-

base persisters – now all forwarding to separate libraries

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.html b/docs/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.html deleted file mode 100644 index 4cb786e..0000000 --- a/docs/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters._postgres_w_psycopg2_in_progress — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters._postgres_w_psycopg2_in_progress

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/arangodb_w_pyarango.html b/docs/module_docs/py2store/persisters/arangodb_w_pyarango.html deleted file mode 100644 index 0323be0..0000000 --- a/docs/module_docs/py2store/persisters/arangodb_w_pyarango.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.arangodb_w_pyarango — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.arangodb_w_pyarango

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/couchdb_w_couchdb.html b/docs/module_docs/py2store/persisters/couchdb_w_couchdb.html deleted file mode 100644 index 28e9585..0000000 --- a/docs/module_docs/py2store/persisters/couchdb_w_couchdb.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.couchdb_w_couchdb — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.couchdb_w_couchdb

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/dropbox_w_dropbox.html b/docs/module_docs/py2store/persisters/dropbox_w_dropbox.html deleted file mode 100644 index 218877a..0000000 --- a/docs/module_docs/py2store/persisters/dropbox_w_dropbox.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.dropbox_w_dropbox — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.dropbox_w_dropbox

-

Forwards to dropboxdol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/dropbox_w_requests.html b/docs/module_docs/py2store/persisters/dropbox_w_requests.html deleted file mode 100644 index 9d0e967..0000000 --- a/docs/module_docs/py2store/persisters/dropbox_w_requests.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.dropbox_w_requests — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.dropbox_w_requests

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/dropbox_w_urllib.html b/docs/module_docs/py2store/persisters/dropbox_w_urllib.html deleted file mode 100644 index f7a15a7..0000000 --- a/docs/module_docs/py2store/persisters/dropbox_w_urllib.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.dropbox_w_urllib — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.dropbox_w_urllib

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/dynamodb_w_boto3.html b/docs/module_docs/py2store/persisters/dynamodb_w_boto3.html deleted file mode 100644 index 1f04bb6..0000000 --- a/docs/module_docs/py2store/persisters/dynamodb_w_boto3.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.dynamodb_w_boto3 — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.dynamodb_w_boto3

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/ftp_persister.html b/docs/module_docs/py2store/persisters/ftp_persister.html deleted file mode 100644 index 810b4e4..0000000 --- a/docs/module_docs/py2store/persisters/ftp_persister.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.ftp_persister — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.ftp_persister

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/googledrive_w_pydrive.html b/docs/module_docs/py2store/persisters/googledrive_w_pydrive.html deleted file mode 100644 index 18dea46..0000000 --- a/docs/module_docs/py2store/persisters/googledrive_w_pydrive.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.googledrive_w_pydrive — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.googledrive_w_pydrive

-

Forwards to pydrivedol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/local_files.html b/docs/module_docs/py2store/persisters/local_files.html deleted file mode 100644 index a861719..0000000 --- a/docs/module_docs/py2store/persisters/local_files.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - - - - - py2store.persisters.local_files — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.local_files

-

base classes to work with local files

-
-
-class py2store.persisters.local_files.DirReader(rootdir)[source]
-

KV Reader whose keys (AND VALUES) are directory full paths of the subdirectories of rootdir.

-
- -
-
-class py2store.persisters.local_files.DirpathFormatKeys(path_format: str, max_levels: int = inf)[source]
-
- -
-
-class py2store.persisters.local_files.FileReader(rootdir)[source]
-

KV Reader whose keys are paths and values are: -- Another FileReader if a path points to a directory -- The bytes of the file if the path points to a file.

-
- -
-
-class py2store.persisters.local_files.FilepathFormatKeys(path_format: str, max_levels: int = inf)[source]
-
- -
-
-exception py2store.persisters.local_files.FolderNotFoundError[source]
-
- -
-
-class py2store.persisters.local_files.LocalFileRWD(mode='', **open_kwargs)[source]
-

A class providing get, set and delete functionality using local files as the storage backend.

-
- -
-
-class py2store.persisters.local_files.LocalFileStreamGetter(**open_kwargs)[source]
-

A class to get stream objects of local open files. -The class can only get keys, and only to read, write (destructive or append).

-
>>> from tempfile import mkdtemp
->>> import os
->>> rootdir = mkdtemp()
->>>
->>> appendable_stream = LocalFileStreamGetter(mode='a+')
->>> reader = PathFormatPersister(rootdir)
->>> filepath = os.path.join(rootdir, 'tmp.txt')
->>>
->>> with appendable_stream[filepath] as fp:
-...     fp.write('hello')
-5
->>> print(reader[filepath])
-hello
->>> with appendable_stream[filepath] as fp:
-...     fp.write(' world')
-6
->>>
->>> print(reader[filepath])
-hello world
-
-
-
- -
-
-class py2store.persisters.local_files.PathFormatPersister(path_format, max_levels: int = inf, mode='', **open_kwargs)[source]
-
- -
-
-class py2store.persisters.local_files.PrefixedDirpathsRecursive[source]
-

Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. -This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)).

-
- -
-
-class py2store.persisters.local_files.PrefixedFilepaths[source]
-

Keys collection for local files, where the keys are full filepaths DIRECTLY under a given root dir _prefix. -This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)).

-
- -
-
-class py2store.persisters.local_files.PrefixedFilepathsRecursive[source]
-

Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. -This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)).

-
- -
-
-py2store.persisters.local_files.ensure_slash_suffix(path: str)[source]
-

Add a file separation (/ or ) at the end of path str, if not already present.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/mongo_w_pymongo.html b/docs/module_docs/py2store/persisters/mongo_w_pymongo.html deleted file mode 100644 index 745f1ed..0000000 --- a/docs/module_docs/py2store/persisters/mongo_w_pymongo.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.mongo_w_pymongo — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.mongo_w_pymongo

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/new_s3.html b/docs/module_docs/py2store/persisters/new_s3.html deleted file mode 100644 index 208eab1..0000000 --- a/docs/module_docs/py2store/persisters/new_s3.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.new_s3 — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.new_s3

-

Forwards to s3dol.new_s3

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/redis_w_redis.html b/docs/module_docs/py2store/persisters/redis_w_redis.html deleted file mode 100644 index 5eb0640..0000000 --- a/docs/module_docs/py2store/persisters/redis_w_redis.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.redis_w_redis — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.redis_w_redis

-

Forwards to redisdol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/s3_w_boto3.html b/docs/module_docs/py2store/persisters/s3_w_boto3.html deleted file mode 100644 index a84199a..0000000 --- a/docs/module_docs/py2store/persisters/s3_w_boto3.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.s3_w_boto3 — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.s3_w_boto3

-

Forwards to s3dol.s3_w_boto3

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/sql_w_odbc.html b/docs/module_docs/py2store/persisters/sql_w_odbc.html deleted file mode 100644 index 6d8fdc7..0000000 --- a/docs/module_docs/py2store/persisters/sql_w_odbc.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.sql_w_odbc — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.sql_w_odbc

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/sql_w_sqlalchemy.html b/docs/module_docs/py2store/persisters/sql_w_sqlalchemy.html deleted file mode 100644 index 6da54c0..0000000 --- a/docs/module_docs/py2store/persisters/sql_w_sqlalchemy.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.sql_w_sqlalchemy — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.sql_w_sqlalchemy

-

Forwards to sqldol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/ssh_persister.html b/docs/module_docs/py2store/persisters/ssh_persister.html deleted file mode 100644 index df866b3..0000000 --- a/docs/module_docs/py2store/persisters/ssh_persister.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.persisters.ssh_persister — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.ssh_persister

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/persisters/w_aiofile.html b/docs/module_docs/py2store/persisters/w_aiofile.html deleted file mode 100644 index ca710c1..0000000 --- a/docs/module_docs/py2store/persisters/w_aiofile.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.persisters.w_aiofile — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.persisters.w_aiofile

-

Forwards to aiofiledol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/scrap/new_gen_local.html b/docs/module_docs/py2store/scrap/new_gen_local.html deleted file mode 100644 index d78130b..0000000 --- a/docs/module_docs/py2store/scrap/new_gen_local.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.scrap.new_gen_local — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.scrap.new_gen_local

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/serializers.html b/docs/module_docs/py2store/serializers.html deleted file mode 100644 index 23c6b19..0000000 --- a/docs/module_docs/py2store/serializers.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.serializers — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.serializers

-

a package of serializers

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/serializers/audio.html b/docs/module_docs/py2store/serializers/audio.html deleted file mode 100644 index 14d3ba5..0000000 --- a/docs/module_docs/py2store/serializers/audio.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.serializers.audio — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.serializers.audio

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/serializers/jsonization.html b/docs/module_docs/py2store/serializers/jsonization.html deleted file mode 100644 index 82e84d6..0000000 --- a/docs/module_docs/py2store/serializers/jsonization.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.serializers.jsonization — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.serializers.jsonization

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/serializers/pickled.html b/docs/module_docs/py2store/serializers/pickled.html deleted file mode 100644 index e29a0e3..0000000 --- a/docs/module_docs/py2store/serializers/pickled.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - py2store.serializers.pickled — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.serializers.pickled

-

functions to pickle objects

-
-
-py2store.serializers.pickled.mk_marshal_rw_funcs(**kwargs)[source]
-

Generates a reader and writer using marshal. That is, a pair of parametrized loads and dumps

-
>>> read, write = mk_marshal_rw_funcs()
->>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2]}}
->>> serialized_d = write(d)
->>> deserialized_d = read(serialized_d)
->>> assert d == deserialized_d
-
-
-
- -
-
-py2store.serializers.pickled.mk_pickle_rw_funcs(fix_imports=True, protocol=None, pickle_encoding='ASCII', pickle_errors='strict')[source]
-

Generates a reader and writer using pickle. That is, a pair of parametrized loads and dumps

-
>>> read, write = mk_pickle_rw_funcs()
->>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2, dict]}}
->>> serialized_d = write(d)
->>> deserialized_d = read(serialized_d)
->>> assert d == deserialized_d
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/serializers/regular_panel_data.html b/docs/module_docs/py2store/serializers/regular_panel_data.html deleted file mode 100644 index 4db6454..0000000 --- a/docs/module_docs/py2store/serializers/regular_panel_data.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.serializers.regular_panel_data — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.serializers.regular_panel_data

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/serializers/sequential.html b/docs/module_docs/py2store/serializers/sequential.html deleted file mode 100644 index 8f3039f..0000000 --- a/docs/module_docs/py2store/serializers/sequential.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.serializers.sequential — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.serializers.sequential

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/signatures.html b/docs/module_docs/py2store/signatures.html deleted file mode 100644 index 946ff1a..0000000 --- a/docs/module_docs/py2store/signatures.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.signatures — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.signatures

-

Forwards to dol.signatures:

-

Signature calculus

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/slib.html b/docs/module_docs/py2store/slib.html deleted file mode 100644 index 22e48fa..0000000 --- a/docs/module_docs/py2store/slib.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.slib — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.slib

-

modules for standard libs

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/slib/s_configparser.html b/docs/module_docs/py2store/slib/s_configparser.html deleted file mode 100644 index 825d0da..0000000 --- a/docs/module_docs/py2store/slib/s_configparser.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.slib.s_configparser — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.slib.s_configparser

-

Data Object Layer for configparser standard lib.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/slib/s_zipfile.html b/docs/module_docs/py2store/slib/s_zipfile.html deleted file mode 100644 index 5c37347..0000000 --- a/docs/module_docs/py2store/slib/s_zipfile.html +++ /dev/null @@ -1,351 +0,0 @@ - - - - - - - - - py2store.slib.s_zipfile — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.slib.s_zipfile

-

a data object layer for zipfile

-
-
-exception py2store.slib.s_zipfile.EmptyZipError[source]
-
- -
-
-class py2store.slib.s_zipfile.FileStreamsOfZip(zip_file, prefix='', open_kws=None)[source]
-

Like FilesOfZip, but object returns are file streams instead. -So you use it like this:

-

``` -z = FileStreamsOfZip(rootdir) -with z[relpath] as fp:

-
-

… # do stuff with fp, like fp.readlines() or such…

-
-

```

-
- -
-
-class py2store.slib.s_zipfile.FilesOfZip(zip_file, prefix='', open_kws=None)[source]
-
- -
-
-class py2store.slib.s_zipfile.FlatZipFilesReader(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, zip_reader=<class 'py2store.slib.s_zipfile.ZipReader'>, **zip_reader_kwargs)[source]
-

Read the union of the contents of multiple zip files. -A local file reader whose keys are the zip filepaths of the rootdir and values are corresponding ZipReaders.

-
- -
-
-exception py2store.slib.s_zipfile.OverwriteNotAllowed[source]
-
- -
-
-py2store.slib.s_zipfile.ZipFileReader
-

alias of py2store.slib.s_zipfile.ZipFilesReader

-
- -
-
-class py2store.slib.s_zipfile.ZipFileStreamsReader(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, *, zip_reader=<class 'py2store.slib.s_zipfile.FileStreamsOfZip'>, **zip_reader_kwargs)
-

Like ZipFilesReader, but objects returned are file streams instead.

-
- -
-
-class py2store.slib.s_zipfile.ZipFilesReader(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, zip_reader=<class 'py2store.slib.s_zipfile.ZipReader'>, **zip_reader_kwargs)[source]
-

A local file reader whose keys are the zip filepaths of the rootdir and values are corresponding ZipReaders.

-
- -
-
-class py2store.slib.s_zipfile.ZipFilesReaderAndBytesWriter(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, zip_reader=<class 'py2store.slib.s_zipfile.ZipReader'>, **zip_reader_kwargs)[source]
-

Like ZipFilesReader, but the ability to write bytes (assumed to be valid bytes of the zip format) to a key

-
- -
-
-class py2store.slib.s_zipfile.ZipReader(zip_file, prefix='', open_kws=None, file_info_filt=None)[source]
-

A KvReader to read the contents of a zip file. -Provides a KV perspective of https://docs.python.org/3/library/zipfile.html

-

ZipReader has two value categories: Directories and Files. -Both categories are distinguishable by the keys, through the “ends with slash” convention.

-

When a file, the value return is bytes, as usual.

-
-
When a directory, the value returned is a ZipReader itself, with all params the same, except for the prefix

which serves to specify the subfolder (that is, ``prefix` acts as a filter).

-
-
-

Note: If you get data zipped by a mac, you might get some junk along with it. -Namely __MACOSX folders .DS_Store files. I won’t rant about it, since others have. -But you might find it useful to remove them from view. One choice is to use py2store.trans.filt_iter -to get a filtered view of the zips contents. In most cases, this should do the job: -` -# applied to store instance or class: -store = filt_iter(filt=lambda x: not x.startswith('__MACOSX') and '.DS_Store' not in x)(store) -`

-

Another option is just to remove these from the zip file once and for all. In unix-like systems: -` -zip -d filename.zip __MACOSX/\* -zip -d filename.zip \*/.DS_Store -`

-

Examples

-

# >>> s = ZipReader(‘/path/to/some_zip_file.zip’) -# >>> len(s) -# 53432 -# >>> list(s)[:3] # the first 3 elements (well… their keys) -# [‘odir/’, ‘odir/app/’, ‘odir/app/data/’] -# >>> list(s)[-3:] # the last 3 elements (well… their keys) -# [‘odir/app/data/audio/d/1574287049078391/m/Ctor.json’, -# ‘odir/app/data/audio/d/1574287049078391/m/intensity.json’, -# ‘odir/app/data/run/status.json’] -# >>> # getting a file (note that by default, you get bytes, so need to decode) -# >>> s[‘odir/app/data/run/status.json’].decode() -# b’{“test_phase_number”: 9, “test_phase”: “TestActions.IGNORE_TEST”, “session_id”: 0}’ -# >>> # when you ask for the contents for a key that’s a directory, -# >>> # you get a ZipReader filtered for that prefix: -# >>> s[‘odir/app/data/audio/’] -# ZipReader(‘/path/to/some_zip_file.zip’, ‘odir/app/data/audio/’, {}, <function take_everything at 0x1538999e0>) -# >>> # Often, you only want files (not directories) -# >>> # You can filter directories out using the file_info_filt argument -# >>> s = ZipReader(‘/path/to/some_zip_file.zip’, file_info_filt=ZipReader.FILES_ONLY) -# >>> len(s) # compare to the 53432 above, that contained dirs too -# 53280 -# >>> list(s)[:3] # first 3 keys are all files now -# [‘odir/app/data/plc/d/1574304926795633/d/1574305026895702’, -# ‘odir/app/data/plc/d/1574304926795633/d/1574305276853053’, -# ‘odir/app/data/plc/d/1574304926795633/d/1574305159343326’] -# >>> -# >>> # ZipReader.FILES_ONLY and ZipReader.DIRS_ONLY are just convenience filt functions -# >>> # Really, you can provide any custom one yourself. -# >>> # This filter function should take a ZipInfo object, and return True or False. -# >>> # (https://docs.python.org/3/library/zipfile.html#zipfile.ZipInfo) -# >>> -# >>> import re -# >>> p = re.compile(‘audio.*.json$’) -# >>> my_filt_func = lambda fileinfo: bool(p.search(fileinfo.filename)) -# >>> s = ZipReader(‘/Users/twhalen/Downloads/2019_11_21.zip’, file_info_filt=my_filt_func) -# >>> len(s) -# 48 -# >>> list(s)[:3] -# [‘odir/app/data/audio/d/1574333557263758/m/Ctor.json’, -# ‘odir/app/data/audio/d/1574333557263758/m/intensity.json’, -# ‘odir/app/data/audio/d/1574288084739961/m/Ctor.json’]

-
- -
-
-class py2store.slib.s_zipfile.ZipStore(zip_filepath, compression=8, allow_overwrites=True, pwd=None)[source]
-

Zip read and writing. -When you want to read zips, there’s the FilesOfZip, ZipReader, or ZipFilesReader we know and love.

-

Sometimes though, you want to write to zips too. For this, we have ZipStore.

-

Since ZipStore can write to a zip, it’s read functionality is not going to assume static data, -and cache things, as your favorite zip readers did. -This, and the acrobatics need to disguise the weird zipfile into something more… key-value natural, -makes for a not so efficient store, out of the box.

-
-
I advise using one of the zip readers if all you need to do is read, or subclassing or

wrapping ZipStore with caching layers if it is appropriate to you.

-
-
-
- -
-
-py2store.slib.s_zipfile.func_conjunction(func1, func2)[source]
-

Returns a function that is equivalent to lambda x: func1(x) and func2(x)

-
- -
-
-py2store.slib.s_zipfile.mk_flatzips_store(dir_of_zips, zip_pair_path_preproc=<built-in function sorted>, mk_store=<class 'py2store.slib.s_zipfile.FlatZipFilesReader'>, **extra_mk_store_kwargs)[source]
-

A store so that you can work with a folder that has a bunch of zip files, -as if they’ve all been extracted in the same folder. -Note that zip_pair_path_preproc can be used to control how to resolve key conflicts -(i.e. when you get two different zip files that have a same path in their contents). -The last path encountered by zip_pair_path_preproc(zip_path_pairs) is the one that will be used, so -one should make zip_pair_path_preproc act accordingly.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/sources.html b/docs/module_docs/py2store/sources.html deleted file mode 100644 index b717170..0000000 --- a/docs/module_docs/py2store/sources.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.sources — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.sources

-

Forwards to dol.sources:

-

This module contains key-value views of disparate sources.

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores.html b/docs/module_docs/py2store/stores.html deleted file mode 100644 index de9c019..0000000 --- a/docs/module_docs/py2store/stores.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.stores — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores

-

a package of various stores

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/arangodb_store.html b/docs/module_docs/py2store/stores/arangodb_store.html deleted file mode 100644 index eff9cc3..0000000 --- a/docs/module_docs/py2store/stores/arangodb_store.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.stores.arangodb_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.arangodb_store

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/couchdb_store.html b/docs/module_docs/py2store/stores/couchdb_store.html deleted file mode 100644 index 1914e04..0000000 --- a/docs/module_docs/py2store/stores/couchdb_store.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.stores.couchdb_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.couchdb_store

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/delegation_stores.html b/docs/module_docs/py2store/stores/delegation_stores.html deleted file mode 100644 index de3681d..0000000 --- a/docs/module_docs/py2store/stores/delegation_stores.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.stores.delegation_stores — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.delegation_stores

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/dropbox_store.html b/docs/module_docs/py2store/stores/dropbox_store.html deleted file mode 100644 index 3cccb15..0000000 --- a/docs/module_docs/py2store/stores/dropbox_store.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.stores.dropbox_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.dropbox_store

-

Forwards to dropboxdol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/local_store.html b/docs/module_docs/py2store/stores/local_store.html deleted file mode 100644 index 1ccb895..0000000 --- a/docs/module_docs/py2store/stores/local_store.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - py2store.stores.local_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.local_store

-

stores to operate on local files

-
-
-class py2store.stores.local_store.AutoMkDirsOnSetitemMixin[source]
-

A mixin that will automatically create directories on setitem, when missing.

-
- -
-
-class py2store.stores.local_store.AutoMkPathformatMixin(path_format=None, max_levels=None)[source]
-

A mixin that will choose a path_format if none given

-
- -
-
-class py2store.stores.local_store.DirStore(rootdir)[source]
-

A store for local directories. -Keys are directory names and values are subdirectory DirStores.

-
>>> from py2store import __file__
->>> import os
->>> root = os.path.dirname(__file__)
->>> s = DirStore(root)
->>> assert set(s).issuperset({'stores', 'persisters', 'serializers', 'key_mappers'})
-
-
-
- -
-
-class py2store.stores.local_store.LocalBinaryStore(path_format, max_levels=None)[source]
-

Local files store for binary data

-
- -
-
-class py2store.stores.local_store.LocalJsonStore(path_format, max_levels=None)[source]
-

Local files store for text dataData is assumed to be a JSON string, and is loaded with json.loads and dumped with json.dumps

-
- -
-
-class py2store.stores.local_store.LocalPickleStore(path_format, max_levels=None, fix_imports=True, protocol=None, pickle_encoding='ASCII', pickle_errors='strict', **open_kwargs)[source]
-

Local files store with pickle serialization

-
- -
-
-py2store.stores.local_store.LocalStore
-

alias of py2store.stores.local_store.QuickPickleStore

-
- -
-
-class py2store.stores.local_store.LocalTextStore(path_format, max_levels=None)[source]
-

Local files store for text data

-
- -
-
-class py2store.stores.local_store.MakeMissingDirsStoreMixin[source]
-

Will make a local file store automatically create the directories needed to create a file. -Should be placed before the concrete perisister in the mro but in such a manner so that it receives full paths.

-
- -
-
-class py2store.stores.local_store.PathFormatStore(path_format, max_levels: int = inf, mode='', **open_kwargs)[source]
-

Local file store using templated relative paths.

-
>>> from tempfile import gettempdir
->>> import os
->>>
->>> def write_to_key(fullpath_of_relative_path, relative_path, content):  # a function to write content in files
-...    with open(fullpath_of_relative_path(relative_path), 'w') as fp:
-...        fp.write(content)
->>>
->>> # Preparation: Make a temporary rootdir and write two files in it
->>> rootdir = os.path.join(gettempdir(), 'path_format_store_test' + os.sep)
->>> if not os.path.isdir(rootdir):
-...     os.mkdir(rootdir)
->>> # recreate directory (remove existing files, delete directory, and re-create it)
->>> for f in os.listdir(rootdir):
-...     fullpath = os.path.join(rootdir, f)
-...     if os.path.isfile(fullpath):
-...         os.remove(os.path.join(rootdir, f))
->>> if os.path.isdir(rootdir):
-...     os.rmdir(rootdir)
->>> if not os.path.isdir(rootdir):
-...    os.mkdir(rootdir)
->>>
->>> filepath_of = lambda p: os.path.join(rootdir, p)  # a function to get a fullpath from a relative one
->>> # and make two files in this new dir, with some content
->>> write_to_key(filepath_of, 'a', 'foo')
->>> write_to_key(filepath_of, 'b', 'bar')
->>>
->>> # point the obj source to the rootdir
->>> s = PathFormatStore(path_format=rootdir)
->>>
->>> # assert things...
->>> assert s._prefix == rootdir  # the _rootdir is the one given in constructor
->>> assert s[filepath_of('a')] == 'foo'  # (the filepath for) 'a' contains 'foo'
->>>
->>> # two files under rootdir (as long as the OS didn't create it's own under the hood)
->>> len(s)
-2
->>> assert list(s) == [filepath_of('a'), filepath_of('b')]  # there's two files in s
->>> filepath_of('a') in s  # rootdir/a is in s
-True
->>> filepath_of('not_there') in s  # rootdir/not_there is not in s
-False
->>> filepath_of('not_there') not in s  # rootdir/not_there is not in s
-True
->>> assert list(s.keys()) == [filepath_of('a'), filepath_of('b')]  # the keys (filepaths) of s
->>> sorted(list(s.values())) # the values of s (contents of files)
-['bar', 'foo']
->>> assert list(s.items()) == [(filepath_of('a'), 'foo'), (filepath_of('b'), 'bar')]  # the (path, content) items
->>> assert s.get('this key is not there', None) is None  # trying to get the val of a non-existing key returns None
->>> s.get('this key is not there', 'some default value')  # ... or whatever you say
-'some default value'
->>>
->>> # add more files to the same folder
->>> write_to_key(filepath_of, 'this.txt', 'this')
->>> write_to_key(filepath_of, 'that.txt', 'blah')
->>> write_to_key(filepath_of, 'the_other.txt', 'bloo')
->>> # see that you now have 5 files
->>> len(s)
-5
->>> # and these files contain values:
->>> sorted(s.values())
-['bar', 'blah', 'bloo', 'foo', 'this']
->>>
->>> # but if we make an obj source to only take files whose extension is '.txt'...
->>> s = PathFormatStore(path_format=rootdir + '{}.txt')
->>>
->>> rootdir_2 = os.path.join(gettempdir(), 'obj_source_test_2') # get another rootdir
->>> if not os.path.isdir(rootdir_2):
-...    os.mkdir(rootdir_2)
->>> filepath_of_2 = lambda p: os.path.join(rootdir_2, p)
->>> # and make two files in this new dir, with some content
->>> write_to_key(filepath_of, 'this.txt', 'this')
->>> write_to_key(filepath_of, 'that.txt', 'blah')
->>> write_to_key(filepath_of, 'the_other.txt', 'bloo')
->>>
->>> ss = PathFormatStore(path_format=rootdir_2 + '{}.txt')
->>>
->>> assert s != ss  # though pointing to identical content, o and oo are not equal since the paths are not equal!
-
-
-
- -
-
-class py2store.stores.local_store.PathFormatStoreWithPrefix(*args, **kwargs)[source]
-
- -
-
-py2store.stores.local_store.PickleStore
-

alias of py2store.stores.local_store.LocalPickleStore

-
- -
-
-class py2store.stores.local_store.QuickBinaryStore(path_format=None, max_levels=None)[source]
-

Local files store for binary data with default temp root and auto dir generation on write.

-
- -
-
-class py2store.stores.local_store.QuickJsonStore(path_format=None, max_levels=None)[source]
-

Local files store for text data with default temp root and auto dir generation on write.Data is assumed to be a JSON string, and is loaded with json.loads and dumped with json.dumps

-
- -
-
-class py2store.stores.local_store.QuickLocalStoreMixin(path_format=None, max_levels=None)[source]
-

A mixin that will choose a path_format if none given, -and will automatically create directories on setitem, when missing.

-
- -
-
-class py2store.stores.local_store.QuickPickleStore(path_format=None, max_levels=None)[source]
-

Local files store with pickle serialization with default temp root and auto dir generation on write.

-
- -
-
-py2store.stores.local_store.QuickStore
-

alias of py2store.stores.local_store.QuickPickleStore

-
- -
-
-class py2store.stores.local_store.QuickTextStore(path_format=None, max_levels=None)[source]
-

Local files store for text data with default temp root and auto dir generation on write.

-
- -
-
-class py2store.stores.local_store.RelativeDirPathFormatKeys(*args, **kwargs)[source]
-
- -
-
-class py2store.stores.local_store.RelativePathFormatStore2(*args, **kwargs)[source]
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/mongo_store.html b/docs/module_docs/py2store/stores/mongo_store.html deleted file mode 100644 index 00e9ab4..0000000 --- a/docs/module_docs/py2store/stores/mongo_store.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.stores.mongo_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.mongo_store

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/s3_store.html b/docs/module_docs/py2store/stores/s3_store.html deleted file mode 100644 index ab90111..0000000 --- a/docs/module_docs/py2store/stores/s3_store.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.stores.s3_store — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.s3_store

-

Forwards to s3dol.s3_store

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/stores/sql_w_sqlalchemy.html b/docs/module_docs/py2store/stores/sql_w_sqlalchemy.html deleted file mode 100644 index 8c0a911..0000000 --- a/docs/module_docs/py2store/stores/sql_w_sqlalchemy.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.stores.sql_w_sqlalchemy — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.stores.sql_w_sqlalchemy

-

Forwards to sqldol

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test.html b/docs/module_docs/py2store/test.html deleted file mode 100644 index 87a0c6e..0000000 --- a/docs/module_docs/py2store/test.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.test — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test

-

test files

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test/local_files_test.html b/docs/module_docs/py2store/test/local_files_test.html deleted file mode 100644 index 71a49be..0000000 --- a/docs/module_docs/py2store/test/local_files_test.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.test.local_files_test — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test.local_files_test

-

testing local files functionality

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test/quick_test.html b/docs/module_docs/py2store/test/quick_test.html deleted file mode 100644 index 5edf833..0000000 --- a/docs/module_docs/py2store/test/quick_test.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.test.quick_test — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test.quick_test

-

a quick test

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test/scrap.html b/docs/module_docs/py2store/test/scrap.html deleted file mode 100644 index 994222d..0000000 --- a/docs/module_docs/py2store/test/scrap.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.test.scrap — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test.scrap

-

scrap code

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test/simple_test.html b/docs/module_docs/py2store/test/simple_test.html deleted file mode 100644 index d8e8eb9..0000000 --- a/docs/module_docs/py2store/test/simple_test.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.test.simple_test — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test.simple_test

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test/trans_test.html b/docs/module_docs/py2store/test/trans_test.html deleted file mode 100644 index d929401..0000000 --- a/docs/module_docs/py2store/test/trans_test.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - py2store.test.trans_test — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test.trans_test

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/test/util.html b/docs/module_docs/py2store/test/util.html deleted file mode 100644 index f522d2b..0000000 --- a/docs/module_docs/py2store/test/util.html +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - py2store.test.util — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.test.util

-

utils for testing

-
-
-py2store.test.util.random_dict_gen(fields=('a', 'b', 'c'), word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n: int = 100)[source]
-

Random dict (of strings) generator

-
-
Parameters
-
    -
  • fields – Field names for the random dicts

  • -
  • word_size_range – An int, 2-tuple of ints, or list-like object that defines the choices of word sizes

  • -
  • alphabet – A string or iterable defining the alphabet to draw from

  • -
  • n – The number of elements the generator will yield

  • -
-
-
Returns
-

Random dict (of strings) generator

-
-
-
- -
-
-py2store.test.util.random_formatted_str_gen(format_string='root/{}/{}_{}.test', word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n=100)[source]
-

Random formatted string generator

-
-
Parameters
-
    -
  • format_string – A format string

  • -
  • word_size_range – An int, 2-tuple of ints, or list-like object that defines the choices of word sizes

  • -
  • alphabet – A string or iterable defining the alphabet to draw from

  • -
  • n – The number of elements the generator will yield

  • -
-
-
Returns
-

Yields random strings of the format defined by format_string

-
-
-

Examples

-

# >>> list(random_formatted_str_gen(‘root/{}/{}_{}.test’, (2, 5), ‘abc’, n=5)) -[(‘root/acba/bb_abc.test’,),

-
-

(‘root/abcb/cbbc_ca.test’,), -(‘root/ac/ac_cc.test’,), -(‘root/aacc/ccbb_ab.test’,), -(‘root/aab/abb_cbab.test’,)]

-
-
>>> # The following will be made not random (by restricting the constraints to "no choice"
->>> # ... this is so that we get consistent outputs to assert for the doc test.
->>>
->>> # Example with automatic specification
->>> list(random_formatted_str_gen('root/{}/{}_{}.test', (3, 4), 'a', n=2))
-[('root/aaa/aaa_aaa.test',), ('root/aaa/aaa_aaa.test',)]
->>>
->>> # Example with manual specification
->>> list(random_formatted_str_gen('indexed field: {0}: named field: {name}', (2, 3), 'z', n=1))
-[('indexed field: zz: named field: zz',)]
-
-
-
- -
-
-py2store.test.util.random_string(length=7, alphabet='abcdefghijklmnopqrstuvwxyz')[source]
-

Same as random_word, but it optimized for strings -(5-10% faster for words of length 7, 25-30% faster for words of size 1000)

-
- -
-
-py2store.test.util.random_tuple_gen(tuple_length=3, word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n: int = 100)[source]
-

Random tuple (of strings) generator

-
-
Parameters
-
    -
  • tuple_length – The length of the tuples generated

  • -
  • word_size_range – An int, 2-tuple of ints, or list-like object that defines the choices of word sizes

  • -
  • alphabet – A string or iterable defining the alphabet to draw from

  • -
  • n – The number of elements the generator will yield

  • -
-
-
Returns
-

Random tuple (of strings) generator

-
-
-
- -
-
-py2store.test.util.random_word(length, alphabet, concat_func=<built-in function add>)[source]
-

Make a random word by concatenating randomly drawn elements from alphabet together -:param length: Length of the word -:param alphabet: Alphabet to draw from -:param concat_func: The concatenation function (e.g. + for strings and lists)

-

Note: Repeated elements in alphabet will have more chances of being drawn.

-
-
Returns
-

A word (whose type depends on what concatenating elements from alphabet produces).

-
-
-

Not making this a proper doctest because I don’t know how to seed the global random temporarily ->>> t = random_word(4, ‘abcde’); # e.g. ‘acae’ ->>> t = random_word(5, [‘a’, ‘b’, ‘c’]); # e.g. ‘cabba’ ->>> t = random_word(4, [[1, 2, 3], [40, 50], [600], [7000]]); # e.g. [40, 50, 7000, 7000, 1, 2, 3] ->>> t = random_word(4, [1, 2, 3, 4]); # e.g. 13 (because adding numbers…) ->>> # … sometimes it’s what you want: ->>> t = random_word(4, [2 ** x for x in range(8)]); # e.g. 105 (binary combination) ->>> t = random_word(4, [1, 2, 3, 4], concat_func=lambda x, y: str(x) + str(y)); # e.g. ‘4213’ ->>> t = random_word(4, [1, 2, 3, 4], concat_func=lambda x, y: int(str(x) + str(y))); # e.g. 3432

-
- -
-
-py2store.test.util.random_word_gen(word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n=100)[source]
-

Random string generator -:param word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes -:param alphabet: A string or iterable defining the alphabet to draw from -:param n: The number of elements the generator will yield

-
-
Returns
-

Random string generator

-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/trans.html b/docs/module_docs/py2store/trans.html deleted file mode 100644 index 1f113f2..0000000 --- a/docs/module_docs/py2store/trans.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.trans — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.trans

-

Forwards to dol.trans:

-

Transformation/wrapping tools

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/util.html b/docs/module_docs/py2store/util.html deleted file mode 100644 index f928c78..0000000 --- a/docs/module_docs/py2store/util.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - py2store.util — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.util

-

Forwards to dol.util:

-

General util objects

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils.html b/docs/module_docs/py2store/utils.html deleted file mode 100644 index c603b90..0000000 --- a/docs/module_docs/py2store/utils.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.utils — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils

-

general utils

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/affine_conversion.html b/docs/module_docs/py2store/utils/affine_conversion.html deleted file mode 100644 index 90f0e8a..0000000 --- a/docs/module_docs/py2store/utils/affine_conversion.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - - py2store.utils.affine_conversion — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.affine_conversion

-

utils to carry out affine transformations (of indices)

-
-
-class py2store.utils.affine_conversion.AffineConverter(scale=1.0, offset=0.0)[source]
-

Getting a callable that will perform an affine conversion. -Note, it does it as

-
-

(val - offset) * scale

-
-

(Note slope-intercept style (though there is the .from_slope_and_intercept constructor method for that)

-
-
Inverse is available through the inv method, performing:

val / scale + offset

-
-
-
>>> convert = AffineConverter(scale=0.5, offset=1)
->>> convert(0)
--0.5
->>> convert(10)
-4.5
->>> convert.inv(4)
-9.0
->>> convert.inv(4.5)
-10.0
-
-
-
- -
-
-py2store.utils.affine_conversion.get_affine_converter_and_inverse(scale=1, offset=0, source_type_cast=None, target_type_cast=None)[source]
-
-
Getting two affine functions with given scale and offset, that are inverse of each other. Namely (for input val):

(val - offset) * scale and val / scale + offset

-
-
-

Note this is not “slope intercept” style!!

-

The source_type_cast and target_type_case (optional), allow the user to specify if these transformations need to -be further cast to a given type. -:param scale: -:param offset: -:param source_type_cast: function to apply to input -:param target_type_cast: function to apply to output -:return: Two single val functions: affine_converter, inverse_affine_converter

-

Note: Code is a lot more complex than the basic operations it performs. The reason was a worry of efficiency since -the functions that are returned are intended to be used in long loops.

-

See also: ocore.utils.conversion.AffineConverter

-
>>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1)
->>> affine_converter(0)
--0.5
->>> affine_converter(10)
-4.5
->>> inverse_affine_converter(4)
-9.0
->>> inverse_affine_converter(4.5)
-10.0
->>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1,target_type_cast=int)
->>> affine_converter(10)
-4
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/appendable.html b/docs/module_docs/py2store/utils/appendable.html deleted file mode 100644 index 9aa6492..0000000 --- a/docs/module_docs/py2store/utils/appendable.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.utils.appendable — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.appendable

-

utils to make add append and extend functionality to KV stores

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/attr_dict.html b/docs/module_docs/py2store/utils/attr_dict.html deleted file mode 100644 index d22cb1d..0000000 --- a/docs/module_docs/py2store/utils/attr_dict.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - py2store.utils.attr_dict — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.attr_dict

-

a data object layer for object attributes

-
-
-class py2store.utils.attr_dict.AttrMap(arg)[source]
-

A read-only façade for navigating a JSON-like object using attribute notation. -Based on Luciano Ramalho’s “Fluent Python” book.

-
>>> t = AttrMap({'a': {'b': 2, 'foo': 'bar'}, 'b': [1,2,3]})
->>> t
-AttrMap({'a': {'b': 2, 'foo': 'bar'}, 'b': [1, 2, 3]})
->>> t.a
-AttrMap({'b': 2, 'foo': 'bar'})
->>> t.a.foo
-'bar'
->>> t.b
-[1, 2, 3]
-
-
-
- -
-
-py2store.utils.attr_dict.attr_wrap(cls, name=None)[source]
-

Returns a Mapping class that routes attribute access to keys of mapping.

-
>>> A = attr_wrap(dict)
->>> t = A({'a_special_attr': 'foo', 'another_attr': 2, # valid identifiers
-...        42: [1, 2], '$invalid': 'identifier', 'class': 'is a reserved keyword'})  # not valid identifiers
->>> # verify that we have the attr we want
->>> assert 'a_special_attr' in dir(t)
->>> assert 'another_attr' in dir(t)
->>> # verify that we DO NOT have the attr we DO NOT want
->>> assert 42 not in dir(t)
->>> assert '$invalid' not in dir(t)
->>> assert 'class' not in dir(t)
-
-
-
- -
-
-py2store.utils.attr_dict.iskeyword()
-

x.__contains__(y) <==> y in x.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/cache_descriptors.html b/docs/module_docs/py2store/utils/cache_descriptors.html deleted file mode 100644 index 408babe..0000000 --- a/docs/module_docs/py2store/utils/cache_descriptors.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - - - py2store.utils.cache_descriptors — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.cache_descriptors

-

descriptors to cache data

-
-
-py2store.utils.cache_descriptors.CachedProperty(*args)[source]
-

CachedProperties. -This is usable directly as a decorator when given names, or when not. Any of these patterns -will work: -* @CachedProperty -* @CachedProperty() -* @CachedProperty('n','n2') -* def thing(self: …; thing = CachedProperty(thing) -* def thing(self: …; thing = CachedProperty(thing, ‘n’)

-
- -
-
-class py2store.utils.cache_descriptors.Lazy(func, name=None)[source]
-

Lazy Attributes.

-
- -
-
-class py2store.utils.cache_descriptors.cachedIn(attribute_name)[source]
-

Cached property with given cache attribute.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/cumul_aggreg_write.html b/docs/module_docs/py2store/utils/cumul_aggreg_write.html deleted file mode 100644 index ddf7563..0000000 --- a/docs/module_docs/py2store/utils/cumul_aggreg_write.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - - - - py2store.utils.cumul_aggreg_write — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.cumul_aggreg_write

-

utils for bulk writing – accumulate, aggregate and write when some condition is met

-
-
-class py2store.utils.cumul_aggreg_write.CumulAggregWrite(store, cache_to_kv=<function mk_kv_from_keygen.<locals>.aggregate>, mk_cache=<class 'list'>)[source]
-
- -
-
-class py2store.utils.cumul_aggreg_write.CumulAggregWriteKvItems(store)[source]
-
- -
-
-class py2store.utils.cumul_aggreg_write.CumulAggregWriteWithAutoFlush(store, cache_to_kv=<function mk_kv_from_keygen.<locals>.aggregate>, mk_cache=<class 'list'>, flush_cache_condition=<function condition_flush_on_every_write>)[source]
-
- -
-
-py2store.utils.cumul_aggreg_write.condition_flush_on_every_write(cache)[source]
-

Boolean function used as flush_cache_condition to anytime the cache is non-empty

-
- -
-
-py2store.utils.cumul_aggreg_write.mk_group_aggregator(item_to_kv, aggregator_op=<built-in function add>, initial=<py2store.utils.cumul_aggreg_write.NoInitial object>)[source]
-

Make a generator transforming function that will -(a) make a key for each given item, -(b) group all items according to the key

-
-
Parameters
-
    -
  • item_to_kv –

  • -
  • aggregator_op –

  • -
  • initial –

  • -
-
-
-

Returns:

-
>>> # Collect words (as a csv string), grouped by the lower case of the first letter
->>> ag = mk_group_aggregator(lambda item: (item[0].lower(), item),
-...                          aggregator_op=lambda x, y: ', '.join([x, y]))
->>> list(ag(['apple', 'bananna', 'Airplane']))
-[('a', 'apple, Airplane'), ('b', 'bananna')]
->>> # Collect (and concatinate)  characters according to their ascii value modulo 3
->>> ag = mk_group_aggregator(lambda item: (item['age'], item['thing']),
-...                          aggregator_op=lambda x, y: x + [y],
-...                          initial=[])
->>> list(ag([{'age': 0, 'thing': 'new'}, {'age': 42, 'thing': 'every'}, {'age': 0, 'thing': 'just born'}]))
-[(0, ['new', 'just born']), (42, ['every'])]
-
-
-
- -
-
-py2store.utils.cumul_aggreg_write.mk_group_aggregator_with_key_func(item_to_key, aggregator_op=<built-in function add>, initial=<py2store.utils.cumul_aggreg_write.NoInitial object>)[source]
-

Make a generator transforming function that will -(a) make a key for each given item, -(b) group all items according to the key

-
-
Parameters
-
    -
  • item_to_key – Function that takes an item of the generator and outputs the key that should be used to group items

  • -
  • aggregator_op – The aggregation binary function that is used to aggregate two items together. -The function is used as is by the functools.reduce, applied to the sequence of items that were collected for -a given group

  • -
  • initial – The “empty” element to start the reduce (aggregation) with, if necessary.

  • -
-
-
-

Returns:

-
>>> # Collect words (as a csv string), grouped by the lower case of the first letter
->>> ag = mk_group_aggregator_with_key_func(lambda item: item[0].lower(),
-...                          aggregator_op=lambda x, y: ', '.join([x, y]))
->>> list(ag(['apple', 'bananna', 'Airplane']))
-[('a', 'apple, Airplane'), ('b', 'bananna')]
->>>
->>> # Collect (and concatenate) characters according to their ascii value modulo 3
-... ag = mk_group_aggregator_with_key_func(lambda item: (ord(item) % 3))
->>> list(ag('abcdefghijklmnop'))
-[(1, 'adgjmp'), (2, 'behkn'), (0, 'cfilo')]
->>>
->>> # sum all even and odd number separately
-... ag = mk_group_aggregator_with_key_func(lambda item: (item % 2))
->>> list(ag([1, 2, 3, 4, 5]))  # sum of evens is 6, and sum of odds is 9
-[(1, 9), (0, 6)]
->>>
->>> # if we wanted to collect all odds and evens, we'd need a different aggregator and initial
-... ag = mk_group_aggregator_with_key_func(lambda item: (item % 2), aggregator_op=lambda x, y: x + [y], initial=[])
->>> list(ag([1, 2, 3, 4, 5]))
-[(1, [1, 3, 5]), (0, [2, 4])]
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/explicit.html b/docs/module_docs/py2store/utils/explicit.html deleted file mode 100644 index f88589d..0000000 --- a/docs/module_docs/py2store/utils/explicit.html +++ /dev/null @@ -1,319 +0,0 @@ - - - - - - - - - py2store.utils.explicit — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.explicit

-

utils to make stores based on a the input data itself

-
-
-class py2store.utils.explicit.ExplicitKeymapReader(store, key_of_id=None, id_of_key=None)[source]
-

Wrap a store (instance) so that it gets it’s keys from an explicit iterable of keys.

-
>>> s = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
->>> id_of_key = {'A': 'a', 'C': 'c'}
->>> ss = ExplicitKeymapReader(s, id_of_key=id_of_key)
->>> list(ss)
-['A', 'C']
->>> ss['C']  # will look up 'C', find 'c', and call the store on that.
-3
-
-
-
- -
-
-class py2store.utils.explicit.ExplicitKeys(key_collection: Collection)[source]
-

py2store.base.Keys implementation that gets it’s keys explicitly from a collection given at initialization time. -The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.)

-
>>> keys = ExplicitKeys(key_collection=['foo', 'bar', 'alice'])
->>> 'foo' in keys
-True
->>> 'not there' in keys
-False
->>> list(keys)
-['foo', 'bar', 'alice']
-
-
-
- -
-
-class py2store.utils.explicit.ExplicitKeysSource(key_collection: Collection, _obj_of_key: Callable)[source]
-

An object source that uses an explicit keys collection and a specified function to read contents for a key.

-
- -
-
-class py2store.utils.explicit.ExplicitKeysStore(store, key_collection)[source]
-

Wrap a store (instance) so that it gets it’s keys from an explicit iterable of keys.

-
>>> s = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
->>> list(s)
-['a', 'b', 'c', 'd']
->>> ss = ExplicitKeysStore(s, ['d', 'a'])
->>> len(ss)
-2
->>> list(ss)
-['d', 'a']
->>> list(ss.values())
-[4, 1]
->>> ss.head()
-('d', 4)
-
-
-
- -
-
-class py2store.utils.explicit.ExplicitKeysWithPrefixRelativization(key_collection, _prefix=None)[source]
-

py2store.base.Keys implementation that gets it’s keys explicitly from a collection given at initialization time. -The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.)

-
>>> from py2store.base import Store
->>> s = ExplicitKeysWithPrefixRelativization(key_collection=['/root/of/foo', '/root/of/bar', '/root/for/alice'])
->>> keys = Store(store=s)
->>> 'of/foo' in keys
-True
->>> 'not there' in keys
-False
->>> list(keys)
-['of/foo', 'of/bar', 'for/alice']
-
-
-
- -
-
-class py2store.utils.explicit.ObjReader(_obj_of_key: Callable)[source]
-

A reader that uses a specified function to get the contents for a given key.

-
>>> # define a contents_of_key that reads stuff from a dict
->>> data = {'foo': 'bar', 42: "everything"}
->>> def read_dict(k):
-...     return data[k]
->>> pr = ObjReader(_obj_of_key=read_dict)
->>> pr['foo']
-'bar'
->>> pr[42]
-'everything'
->>>
->>> # define contents_of_key that reads stuff from a file given it's path
->>> def read_file(path):
-...     with open(path) as fp:
-...         return fp.read()
->>> pr = ObjReader(_obj_of_key=read_file)
->>> file_where_this_code_is = __file__  # it should be THIS file you're reading right now!
->>> print(pr[file_where_this_code_is][62:155])  # print some characters of this file
-from collections.abc import Mapping
-from typing import Callable, Collection as CollectionType
-
-
-
- -
-
-py2store.utils.explicit.invertible_maps(mapping=None, inv_mapping=None)[source]
-

Returns two maps that are inverse of each other. -Raises an AssertionError iif both maps are None, or if the maps are not inverse of each other

-

Get a pair of invertible maps ->>> invertible_maps({1: 11, 2: 22}) -({1: 11, 2: 22}, {11: 1, 22: 2}) ->>> invertible_maps(None, {11: 1, 22: 2}) -({1: 11, 2: 22}, {11: 1, 22: 2})

-

If two maps are given and invertible, you just get them back ->>> invertible_maps({1: 11, 2: 22}, {11: 1, 22: 2}) -({1: 11, 2: 22}, {11: 1, 22: 2})

-

Or if they’re not invertible ->>> invertible_maps({1: 11, 2: 22}, {11: 1, 22: ‘ha, not what you expected!’}) -Traceback (most recent call last):

-
-

…

-
-

AssertionError: mapping and inv_mapping are not inverse of each other!

-
>>> invertible_maps(None, None)
-Traceback (most recent call last):
-  ...
-ValueError: You need to specify one or both maps
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/glom.html b/docs/module_docs/py2store/utils/glom.html deleted file mode 100644 index c89bfbb..0000000 --- a/docs/module_docs/py2store/utils/glom.html +++ /dev/null @@ -1,1182 +0,0 @@ - - - - - - - - - py2store.utils.glom — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.glom

-

glom is a util to extract stuff from nested structures. -It’s one of those excellent utils that I’ve written many times, but never got quite right. -Mahmoud Hashemi got it right.

-
-
BEGIN LICENSE
-

-
-

Copyright (c) 2018, Mahmoud Hashemi

-

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.

  • -
  • The names of the contributors may not be used to endorse or -promote products derived from this software without specific -prior written permission.

  • -
-
-

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 -OWNER 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.

-
-
END LICENSE
-

-
-

Now, at the time of writing this, I’ve already transformed it to bend it to my liking. -At some point it may become something else, but I wanted there to be a trace of what my seed was. -Though I can’t promise I’ll maintain the same functionality as I transform this module, here’s -a tutorial on how to use it in it’s original form:

-
-
-

I only took the main (core) module from the glom project. -Here’s the original docs of this glom module.

-

If there was ever a Python example of “big things come in small -packages”, glom might be it.

-

The glom package has one central entrypoint, -glom.glom(). Everything else in the package revolves around that -one function.

-

A couple of conventional terms you’ll see repeated many times below:

-
    -
  • target - glom is built to work on any data, so we simply -refer to the object being accessed as the “target”

  • -
  • spec - (aka “glomspec”, short for specification) The -accompanying template used to specify the structure of the return -value.

  • -
-

Now that you know the terms, let’s take a look around glom’s powerful -semantics.

-
-
-class py2store.utils.glom.Auto(spec=None)[source]
-

Switch to Auto mode (the default)

-

TODO: this seems like it should be a sub-class of class Spec() – -if Spec() could help define the interface for new “modes” or dialects -that would also help make match mode feel less duct-taped on

-
- -
-
-class py2store.utils.glom.Call(func=None, args=None, kwargs=None)[source]
-

Call specifies when a target should be passed to a function, -func.

-

Call is similar to partial() in that -it is no more powerful than lambda or other functions, but -it is designed to be more readable, with a better repr.

-
-
Parameters
-

func (callable) – a function or other callable to be called with -the target

-
-
-

Call combines well with T to construct objects. For -instance, to generate a dict and then pass it to a constructor:

-
>>> class ExampleClass(object):
-...    def __init__(self, attr):
-...        self.attr = attr
-...
->>> target = {'attr': 3.14}
->>> glom(target, Call(ExampleClass, kwargs=T)).attr
-3.14
-
-
-

This does the same as glom(target, lambda target: -ExampleClass(**target)), but it’s easy to see which one reads -better.

-
-

Note

-

Call is mostly for functions. Use a T object -if you need to call a method.

-
-
-

Warning

-

Call has a successor with a fuller-featured API, new -in 19.3.0: the Invoke specifier type.

-
-
-
-glomit(target, scope)[source]
-

run against the current target

-
- -
- -
-
-class py2store.utils.glom.Check(spec=T, **kwargs)[source]
-

Check objects are used to make assertions about the target data, -and either pass through the data or raise exceptions if there is a -problem.

-

If any check condition fails, a CheckError is raised.

-
-
Parameters
-
    -
  • spec – a sub-spec to extract the data to which other assertions will -be checked (defaults to applying checks to the target itself)

  • -
  • type – a type or sequence of types to be checked for exact match

  • -
  • equal_to – a value to be checked for equality match (“==”)

  • -
  • validate – a callable or list of callables, each representing a -check condition. If one or more return False or raise an -exception, the Check will fail.

  • -
  • instance_of – a type or sequence of types to be checked with isinstance()

  • -
  • one_of – an iterable of values, any of which can match the target (“in”)

  • -
  • default – an optional default value to replace the value when the check fails -(if default is not specified, GlomCheckError will be raised)

  • -
-
-
-

Aside from spec, all arguments are keyword arguments. Each -argument, except for default, represent a check -condition. Multiple checks can be passed, and if all check -conditions are left unset, Check defaults to performing a basic -truthy check on the value.

-
- -
-
-exception py2store.utils.glom.CheckError(msgs, check, path)[source]
-

This GlomError subtype is raised when target data fails to -pass a Check’s specified validation.

-

An uncaught CheckError looks like this:

-
>>> target = {'a': {'b': 'c'}}
->>> glom(target, {'b': ('a.b', Check(type=int))})  
-Traceback (most recent call last):
-...
-glom.CheckError: target at path ['a.b'] failed check, got error: "expected type to be 'int', found type 'str'"
-
-
-

If the Check contains more than one condition, there may be -more than one error message. The string rendition of the -CheckError will include all messages.

-

You can also catch the CheckError and programmatically access -messages through the msgs attribute on the CheckError -instance.

-
-

Note

-

As of 2018-07-05 (glom v18.2.0), the validation subsystem is -still very new. Exact error message formatting may be enhanced -in future releases.

-
-
- -
-
-class py2store.utils.glom.Coalesce(*subspecs, **kwargs)[source]
-

Coalesce objects specify fallback behavior for a list of -subspecs.

-

Subspecs are passed as positional arguments, and keyword arguments -control defaults. Each subspec is evaluated in turn, and if none -match, a CoalesceError is raised, or a default is returned, -depending on the options used.

-
-

Note

-

This operation may seem very familar if you have experience with -SQL or even C# and others.

-
-

In practice, this fallback behavior’s simplicity is only surpassed -by its utility:

-
>>> target = {'c': 'd'}
->>> glom(target, Coalesce('a', 'b', 'c'))
-'d'
-
-
-

glom tries to get 'a' from target, but gets a -KeyError. Rather than raise a PathAccessError as usual, -glom coalesces into the next subspec, 'b'. The process -repeats until it gets to 'c', which returns our value, -'d'. If our value weren’t present, we’d see:

-
>>> target = {}
->>> glom(target, Coalesce('a', 'b'))  
-Traceback (most recent call last):
-...
-glom.CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) (at path [])
-
-
-

Same process, but because target is empty, we get a -CoalesceError. If we want to avoid an exception, and we -know which value we want by default, we can set default:

-
>>> target = {}
->>> glom(target, Coalesce('a', 'b', 'c'), default='d-fault')
-'d-fault'
-
-
-

'a', 'b', and 'c' weren’t present so we got 'd-fault'.

-
-
Parameters
-
    -
  • subspecs – One or more glommable subspecs

  • -
  • default – A value to return if no subspec results in a valid value

  • -
  • default_factory – A callable whose result will be returned as a default

  • -
  • skip – A value, tuple of values, or predicate function -representing values to ignore

  • -
  • skip_exc – An exception or tuple of exception types to catch and -move on to the next subspec. Defaults to GlomError, the -parent type of all glom runtime exceptions.

  • -
-
-
-

If all subspecs produce skipped values or exceptions, a -CoalesceError will be raised. For more examples, check out -the tutorial, which makes extensive use of Coalesce.

-
- -
-
-exception py2store.utils.glom.CoalesceError(coal_obj, skipped, path)[source]
-

This GlomError subtype is raised from within a -Coalesce spec’s processing, when none of the subspecs -match and no default is provided.

-

The exception object itself keeps track of several values which -may be useful for processing:

-
-
Parameters
-
    -
  • coal_obj (Coalesce) – The original failing spec, see -Coalesce’s docs for details.

  • -
  • skipped (list) – A list of ignored values and exceptions, in the -order that their respective subspecs appear in the original -coal_obj.

  • -
  • path – Like many GlomErrors, this exception knows the path at -which it occurred.

  • -
-
-
-
>>> target = {}
->>> glom(target, Coalesce('a', 'b'))  
-Traceback (most recent call last):
-...
-glom.CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) ...
-
-
-
- -
-
-class py2store.utils.glom.Fill(spec=None)[source]
-

A specifier type which switches to glom into “fill-mode”. For the -spec contained within the Fill, glom will only interpret explicit -specifier types (including T objects). Whereas the default mode -has special interpretations for each of these builtins, fill-mode -takes a lighter touch, making Fill great for “filling out” Python -literals, like tuples, dicts, sets, and lists.

-
>>> target = {'data': [0, 2, 4]}
->>> spec = Fill((T['data'][2], T['data'][0]))
->>> glom(target, spec)
-(4, 0)
-
-
-

As you can see, glom’s usual built-in tuple item chaining behavior -has switched into a simple tuple constructor.

-

(Sidenote for Lisp fans: Fill is like glom’s quasi-quoting.)

-
- -
-
-exception py2store.utils.glom.GlomError[source]
-

The base exception for all the errors that might be raised from -glom() processing logic.

-

By default, exceptions raised from within functions passed to glom -(e.g., len, sum, any lambda) will not be wrapped in a -GlomError.

-
- -
-
-class py2store.utils.glom.Glommer(**kwargs)[source]
-

All the wholesome goodness that it takes to make glom work. This -type mostly serves to encapsulate the type registration context so -that advanced uses of glom don’t need to worry about stepping on -each other’s toes.

-

Glommer objects are lightweight and, once instantiated, provide -the glom() method we know and love:

-
>>> glommer = Glommer()
->>> glommer.glom({}, 'a.b.c', default='d')
-'d'
->>> Glommer().glom({'vals': list(range(3))}, ('vals', len))
-3
-
-
-

Instances also provide register() method for -localized control over type handling.

-
-
Parameters
-

register_default_types (bool) – Whether or not to enable the -handling behaviors of the default glom(). These -default actions include dict access, list and iterable -iteration, and generic object attribute access. Defaults to -True.

-
-
-
-
-register(target_type, **kwargs)[source]
-

Register target_type so glom() will -know how to handle instances of that type as targets.

-
-
Parameters
-
    -
  • target_type (type) – A type expected to appear in a glom() -call target

  • -
  • get (callable) – A function which takes a target object and -a name, acting as a default accessor. Defaults to -getattr().

  • -
  • iterate (callable) – A function which takes a target object -and returns an iterator. Defaults to iter() if -target_type appears to be iterable.

  • -
  • exact (bool) – Whether or not to match instances of subtypes -of target_type.

  • -
-
-
-
-

Note

-

The module-level register() function affects the -module-level glom() function’s behavior. If this -global effect is undesirable for your application, or -you’re implementing a library, consider instantiating a -Glommer instance, and using the -register() and Glommer.glom() -methods instead.

-
-
- -
- -
-
-class py2store.utils.glom.Inspect(*a, **kw)[source]
-

The Inspect specifier type provides a way to get -visibility into glom’s evaluation of a specification, enabling -debugging of those tricky problems that may arise with unexpected -data.

-

Inspect can be inserted into an existing spec in one of two -ways. First, as a wrapper around the spec in question, or second, -as an argument-less placeholder wherever a spec could be.

-

Inspect supports several modes, controlled by -keyword arguments. Its default, no-argument mode, simply echos the -state of the glom at the point where it appears:

-
>>> target = {'a': {'b': {}}}
->>> val = glom(target, Inspect('a.b'))  # wrapping a spec
----
-path:   ['a.b']
-target: {'a': {'b': {}}}
-output: {}
----
-
-
-

Debugging behavior aside, Inspect has no effect on -values in the target, spec, or result.

-
-
Parameters
-
    -
  • echo (bool) – Whether to print the path, target, and output of -each inspected glom. Defaults to True.

  • -
  • recursive (bool) – Whether or not the Inspect should be applied -at every level, at or below the spec that it wraps. Defaults -to False.

  • -
  • breakpoint (bool) – This flag controls whether a debugging prompt -should appear before evaluating each inspected spec. Can also -take a callable. Defaults to False.

  • -
  • post_mortem (bool) – This flag controls whether exceptions -should be caught and interactively debugged with pdb on -inspected specs.

  • -
-
-
-

All arguments above are keyword-only to avoid overlap with a -wrapped spec.

-
-

Note

-

Just like pdb.set_trace(), be careful about leaving stray -Inspect() instances in production glom specs.

-
-
- -
-
-class py2store.utils.glom.Invoke(func)[source]
-

Specifier type designed for easy invocation of callables from glom.

-
-
Parameters
-

func (callable) – A function or other callable object.

-
-
-

Invoke is similar to functools.partial(), but with the -ability to set up a “templated” call which interleaves constants and -glom specs.

-

For example, the following creates a spec which can be used to -check if targets are integers:

-
>>> is_int = Invoke(isinstance).specs(T).constants(int)
->>> glom(5, is_int)
-True
-
-
-

And this composes like any other glom spec:

-
>>> target = [7, object(), 9]
->>> glom(target, [is_int])
-[True, False, True]
-
-
-

Another example, mixing positional and keyword arguments:

-
>>> spec = Invoke(sorted).specs(T).constants(key=int, reverse=True)
->>> target = ['10', '5', '20', '1']
->>> glom(target, spec)
-['20', '10', '5', '1']
-
-
-

Invoke also helps with evaluating zero-argument functions:

-
>>> glom(target={}, spec=Invoke(int))
-0
-
-
-

(A trivial example, but from timestamps to UUIDs, zero-arg calls do come up!)

-
-

Note

-

Invoke is mostly for functions, object construction, and callable -objects. For calling methods, consider the T object.

-
-
-
-constants(*a, **kw)[source]
-

Returns a new Invoke spec, with the provided positional -and keyword argument values stored for passing to the -underlying function.

-
>>> spec = Invoke(T).constants(5)
->>> glom(range, (spec, list))
-[0, 1, 2, 3, 4]
-
-
-

Subsequent positional arguments are appended:

-
>>> spec = Invoke(T).constants(2).constants(10, 2)
->>> glom(range, (spec, list))
-[2, 4, 6, 8]
-
-
-

Keyword arguments also work as one might expect:

-
>>> round_2 = Invoke(round).constants(ndigits=2).specs(T)
->>> glom(3.14159, round_2)
-3.14
-
-
-

constants() and other Invoke -methods may be called multiple times, just remember that every -call returns a new spec.

-
- -
-
-classmethod specfunc(spec)[source]
-

Creates an Invoke instance where the function is -indicated by a spec.

-
>>> spec = Invoke.specfunc('func').constants(5)
->>> glom({'func': range}, (spec, list))
-[0, 1, 2, 3, 4]
-
-
-
- -
-
-specs(*a, **kw)[source]
-

Returns a new Invoke spec, with the provided positional -and keyword arguments stored to be interpreted as specs, with -the results passed to the underlying function.

-
>>> spec = Invoke(range).specs('value')
->>> glom({'value': 5}, (spec, list))
-[0, 1, 2, 3, 4]
-
-
-

Subsequent positional arguments are appended:

-
>>> spec = Invoke(range).specs('start').specs('end', 'step')
->>> target = {'start': 2, 'end': 10, 'step': 2}
->>> glom(target, (spec, list))
-[2, 4, 6, 8]
-
-
-

Keyword arguments also work as one might expect:

-
>>> multiply = lambda x, y: x * y
->>> times_3 = Invoke(multiply).constants(y=3).specs(x='value')
->>> glom({'value': 5}, times_3)
-15
-
-
-

specs() and other Invoke -methods may be called multiple times, just remember that every -call returns a new spec.

-
- -
-
-star(args=None, kwargs=None)[source]
-

Returns a new Invoke spec, with args and/or kwargs -specs set to be “starred” or “star-starred” (respectively)

-
>>> import os.path
->>> spec = Invoke(os.path.join).star(args='path')
->>> target = {'path': ['path', 'to', 'dir']}
->>> glom(target, spec)
-'path/to/dir'
-
-
-
-
Parameters
-
    -
  • args (spec) – A spec to be evaluated and “starred” into the -underlying function.

  • -
  • kwargs (spec) – A spec to be evaluated and “star-starred” into -the underlying function.

  • -
-
-
-

One or both of the above arguments should be set.

-

The star(), like other Invoke -methods, may be called multiple times. The args and kwargs -will be stacked in the order in which they are provided.

-
- -
- -
-
-class py2store.utils.glom.Let(**kw)[source]
-

This specifier type assigns variables to the scope.

-
>>> target = {'data': {'val': 9}}
->>> spec = (Let(value=T['data']['val']), {'val': S['value']})
->>> glom(target, spec)
-{'val': 9}
-
-
-
- -
-
-class py2store.utils.glom.Literal(value)[source]
-

Literal objects specify literal values in rare cases when part of -the spec should not be interpreted as a glommable -subspec. Wherever a Literal object is encountered in a spec, it is -replaced with its wrapped value in the output.

-
>>> target = {'a': {'b': 'c'}}
->>> spec = {'a': 'a.b', 'readability': Literal('counts')}
->>> pprint(glom(target, spec))
-{'a': 'c', 'readability': 'counts'}
-
-
-

Instead of accessing 'counts' as a key like it did with -'a.b', glom() just unwrapped the literal and -included the value.

-

Literal takes one argument, the literal value that should appear -in the glom output.

-

This could also be achieved with a callable, e.g., lambda x: -'literal_string' in the spec, but using a Literal -object adds explicitness, code clarity, and a clean repr().

-
- -
-
-class py2store.utils.glom.Path(*path_parts)[source]
-

Path objects specify explicit paths when the default -'a.b.c'-style general access syntax won’t work or isn’t -desirable. Use this to wrap ints, datetimes, and other valid -keys, as well as strings with dots that shouldn’t be expanded.

-
>>> target = {'a': {'b': 'c', 'd.e': 'f', 2: 3}}
->>> glom(target, Path('a', 2))
-3
->>> glom(target, Path('a', 'd.e'))
-'f'
-
-
-

Paths can be used to join together other Path objects, as -well as T objects:

-
>>> Path(T['a'], T['b'])
-T['a']['b']
->>> Path(Path('a', 'b'), Path('c', 'd'))
-Path('a', 'b', 'c', 'd')
-
-
-

Paths also support indexing and slicing, with each access -returning a new Path object:

-
>>> path = Path('a', 'b', 1, 2)
->>> path[0]
-Path('a')
->>> path[-2:]
-Path(1, 2)
-
-
-
-
-from_t()[source]
-

return the same path but starting from T

-
- -
-
-classmethod from_text(text)[source]
-

Make a Path from .-delimited text:

-
>>> Path.from_text('a.b.c')
-Path('a', 'b', 'c')
-
-
-
- -
-
-items()[source]
-

Returns a tuple of (operation, value) pairs.

-
>>> Path(T.a.b, 'c', T['d']).items()
-(('.', 'a'), ('.', 'b'), ('P', 'c'), ('[', 'd'))
-
-
-
- -
-
-values()[source]
-

Returns a tuple of values referenced in this path.

-
>>> Path(T.a.b, 'c', T['d']).values()
-('a', 'b', 'c', 'd')
-
-
-
- -
- -
-
-exception py2store.utils.glom.PathAccessError(exc, path, part_idx)[source]
-

This GlomError subtype represents a failure to access an -attribute as dictated by the spec. The most commonly-seen error -when using glom, it maintains a copy of the original exception and -produces a readable error message for easy debugging.

-

If you see this error, you may want to:

-
-
    -
  • Check the target data is accurate using Inspect

  • -
  • Catch the exception and return a semantically meaningful error message

  • -
  • Use glom.Coalesce to specify a default

  • -
  • Use the top-level default kwarg on glom()

  • -
-
-

In any case, be glad you got this error and not the one it was -wrapping!

-
-
Parameters
-
    -
  • exc (Exception) – The error that arose when we tried to access -path. Typically an instance of KeyError, AttributeError, -IndexError, or TypeError, and sometimes others.

  • -
  • path (Path) – The full Path glom was in the middle of accessing -when the error occurred.

  • -
  • part_idx (int) – The index of the part of the path that caused -the error.

  • -
-
-
-
>>> target = {'a': {'b': None}}
->>> glom(target, 'a.b.c')  
-Traceback (most recent call last):
-...
-glom.PathAccessError: could not access 'c', part 2 of Path('a', 'b', 'c'), got error: ...
-
-
-
- -
-
-class py2store.utils.glom.Spec(spec, scope=None)[source]
-

Spec objects serve three purposes, here they are, roughly ordered -by utility:

-
-
    -
  1. As a form of compiled or “curried” glom call, similar to -Python’s built-in re.compile().

  2. -
  3. A marker as an object as representing a spec rather than a -literal value in certain cases where that might be ambiguous.

  4. -
  5. A way to update the scope within another Spec.

  6. -
-
-

In the second usage, Spec objects are the complement to -Literal, wrapping a value and marking that it -should be interpreted as a glom spec, rather than a literal value. -This is useful in places where it would be interpreted as a value -by default. (Such as T[key], Call(func) where key and func are -assumed to be literal values and not specs.)

-
-
Parameters
-
    -
  • spec – The glom spec.

  • -
  • scope (dict) – additional values to add to the scope when -evaluating this Spec

  • -
-
-
-
- -
-
-class py2store.utils.glom.TType[source]
-

T, short for “target”. A singleton object that enables -object-oriented expression of a glom specification.

-
-

Note

-

T is a singleton, and does not need to be constructed.

-
-

Basically, think of T as your data’s stunt double. Everything -that you do to T will be recorded and executed during the -glom() call. Take this example:

-
>>> spec = T['a']['b']['c']
->>> target = {'a': {'b': {'c': 'd'}}}
->>> glom(target, spec)
-'d'
-
-
-

So far, we’ve relied on the 'a.b.c'-style shorthand for -access, or used the Path objects, but if you want -to explicitly do attribute and key lookups, look no further than -T.

-

But T doesn’t stop with unambiguous access. You can also call -methods and perform almost any action you would with a normal -object:

-
>>> spec = ('a', (T['b'].items(), list))  # reviewed below
->>> glom(target, spec)
-[('c', 'd')]
-
-
-

A T object can go anywhere in the spec. As seen in the example -above, we access 'a', use a T to get 'b' and iterate -over its items, turning them into a list.

-

You can even use T with Call to construct objects:

-
>>> class ExampleClass(object):
-...    def __init__(self, attr):
-...        self.attr = attr
-...
->>> target = {'attr': 3.14}
->>> glom(target, Call(ExampleClass, kwargs=T)).attr
-3.14
-
-
-

On a further note, while lambda works great in glom specs, and -can be very handy at times, T and Call -eliminate the need for the vast majority of lambda usage with -glom.

-

Unlike lambda and other functions, T roundtrips -beautifully and transparently:

-
>>> T['a'].b['c']('success')
-T['a'].b['c']('success')
-
-
-

T-related access errors raise a PathAccessError -during the glom() call.

-
-

Note

-

While T is clearly useful, powerful, and here to stay, its -semantics are still being refined. Currently, operations beyond -method calls and attribute/item access are considered -experimental and should not be relied upon.

-
-
- -
-
-class py2store.utils.glom.TargetRegistry(register_default_types=True)[source]
-

responsible for registration of target types for iteration -and attribute walking

-
-
-get_handler(op, obj, path=None, raise_exc=True)[source]
-

for an operation and object instance, obj, return the -closest-matching handler function, raising UnregisteredTarget -if no handler can be found for obj (or False if -raise_exc=False)

-
- -
-
-register_op(op_name, auto_func=None, exact=False)[source]
-

add operations beyond the builtins (‘get’ and ‘iterate’ at the time -of writing).

-

auto_func is a function that when passed a type, returns a -handler associated with op_name if it’s supported, or False if -it’s not.

-

See glom.core.register_op() for the global version used by -extensions.

-
- -
- -
-
-exception py2store.utils.glom.UnregisteredTarget(op, target_type, type_map, path)[source]
-

This GlomError subtype is raised when a spec calls for an -unsupported action on a target type. For instance, trying to -iterate on an non-iterable target:

-
>>> glom(object(), ['a.b.c'])  
-Traceback (most recent call last):
-...
-glom.UnregisteredTarget: target type 'object' not registered for 'iterate', expected one of registered types: (...)
-
-
-

It should be noted that this is a pretty uncommon occurrence in -production glom usage. See the setup-and-registration -section for details on how to avoid this error.

-

An UnregisteredTarget takes and tracks a few values:

-
-
Parameters
-
    -
  • op (str) – The name of the operation being performed (‘get’ or ‘iterate’)

  • -
  • target_type (type) – The type of the target being processed.

  • -
  • type_map (dict) – A mapping of target types that do support this operation

  • -
  • path – The path at which the error occurred.

  • -
-
-
-
- -
-
-py2store.utils.glom.glom(target, spec, **kwargs)[source]
-

Access or construct a value from a given target based on the -specification declared by spec.

-

Accessing nested data, aka deep-get:

-
>>> target = {'a': {'b': 'c'}}
->>> glom(target, 'a.b')
-'c'
-
-
-

Here the spec was just a string denoting a path, -'a.b.. As simple as it should be. The next example shows -how to use nested data to access many fields at once, and make -a new nested structure.

-

Constructing, or restructuring more-complicated nested data:

-
>>> target = {'a': {'b': 'c', 'd': 'e'}, 'f': 'g', 'h': [0, 1, 2]}
->>> spec = {'a': 'a.b', 'd': 'a.d', 'h': ('h', [lambda x: x * 2])}
->>> output = glom(target, spec)
->>> pprint(output)
-{'a': 'c', 'd': 'e', 'h': [0, 2, 4]}
-
-
-

glom also takes a keyword-argument, default. When set, -if a glom operation fails with a GlomError, the -default will be returned, very much like -dict.get():

-
>>> glom(target, 'a.xx', default='nada')
-'nada'
-
-
-

The skip_exc keyword argument controls which errors should -be ignored.

-
>>> glom({}, lambda x: 100.0 / len(x), default=0.0, skip_exc=ZeroDivisionError)
-0.0
-
-
-
-
Parameters
-
    -
  • target (object) – the object on which the glom will operate.

  • -
  • spec (object) – Specification of the output object in the form -of a dict, list, tuple, string, other glom construct, or -any composition of these.

  • -
  • default (object) – An optional default to return in the case -an exception, specified by skip_exc, is raised.

  • -
  • skip_exc (Exception) – An optional exception or tuple of -exceptions to ignore and return default (None if -omitted). If skip_exc and default are both not set, -glom raises errors through.

  • -
  • scope (dict) – Additional data that can be accessed -via S inside the glom-spec.

  • -
-
-
-

It’s a small API with big functionality, and glom’s power is -only surpassed by its intuitiveness. Give it a whirl!

-
- -
-
-py2store.utils.glom.is_iterable(x)[source]
-

Similar in nature to callable(), is_iterable returns -True if an object is `iterable`_, False if not. ->>> is_iterable([]) -True ->>> is_iterable(1) -False

-
- -
-
-py2store.utils.glom.make_sentinel(name='_MISSING', var_name=None)[source]
-

Creates and returns a new instance of a new class, suitable for -usage as a “sentinel”, a kind of singleton often used to indicate -a value is missing when None is a valid input.

-
-
Parameters
-
    -
  • name (str) – Name of the Sentinel

  • -
  • var_name (str) – Set this name to the name of the variable in -its respective module enable pickleability.

  • -
-
-
-
>>> make_sentinel(var_name='_MISSING')
-_MISSING
-
-
-

The most common use cases here in boltons are as default values -for optional function arguments, partly because of its -less-confusing appearance in automatically generated -documentation. Sentinels also function well as placeholders in queues -and linked lists.

-
-

Note

-

By design, additional calls to make_sentinel with the same -values will not produce equivalent objects.

-
>>> make_sentinel('TEST') == make_sentinel('TEST')
-False
->>> type(make_sentinel('TEST')) == type(make_sentinel('TEST'))
-False
-
-
-
-
- -
-
-py2store.utils.glom.register(target_type, **kwargs)[source]
-

Register target_type so glom() will -know how to handle instances of that type as targets.

-
-
Parameters
-
    -
  • target_type (type) – A type expected to appear in a glom() -call target

  • -
  • get (callable) – A function which takes a target object and -a name, acting as a default accessor. Defaults to -getattr().

  • -
  • iterate (callable) – A function which takes a target object -and returns an iterator. Defaults to iter() if -target_type appears to be iterable.

  • -
  • exact (bool) – Whether or not to match instances of subtypes -of target_type.

  • -
-
-
-
-

Note

-

The module-level register() function affects the -module-level glom() function’s behavior. If this -global effect is undesirable for your application, or -you’re implementing a library, consider instantiating a -Glommer instance, and using the -register() and Glommer.glom() -methods instead.

-
-
- -
-
-py2store.utils.glom.register_op(op_name, **kwargs)[source]
-

For extension authors needing to add operations beyond the builtin -‘get’ and ‘iterate’ to the default scope. See TargetRegistry for more details.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/mappify.html b/docs/module_docs/py2store/utils/mappify.html deleted file mode 100644 index 61bb245..0000000 --- a/docs/module_docs/py2store/utils/mappify.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - - - - py2store.utils.mappify — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.mappify

-

Utils to wrap any object into a mapping interface

-
-
-class py2store.utils.mappify.LeafMappify(target, node_types=(<class 'dict'>, ), key_concat=<function Mappify.<lambda>>, names_of_literals=(), **kwargs)[source]
-

A dict-like interface to glom. Here, only leaf keys are taken into account.

-
>>> d = {
-...     'a': 'simple',
-...     'b': {'is': 'nested'},
-...     'c': {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]}
-... }
->>> g = LeafMappify(d)
->>>
->>> assert list(g) == ['a', 'b.is', 'c.is', 'c.and', 'c.a']
->>> assert g['a'] == 'simple'
->>> assert g['b.is'] == 'nested'
->>> assert g['c.a'] == [1, 2, 3]
->>>
->>> for k, v in g.items():
-...     print(f"{k}: {v}")
-...
-a: simple
-b.is: nested
-c.is: nested
-c.and: has
-c.a: [1, 2, 3]
-
-
-
- -
-
-class py2store.utils.mappify.Mappify(target, node_types=(<class 'dict'>, ), key_concat=<function Mappify.<lambda>>, names_of_literals=(), **kwargs)[source]
-
>>> d = {
-...     'a': 'simple',
-...     'b': {'is': 'nested'},
-...     'c': {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]}
-... }
->>> g = Mappify(d)
->>>
->>> assert list(g) == ['a', 'b.is', 'b', 'c.is', 'c.and', 'c.a', 'c']
->>> assert g['a'] == 'simple'
->>> assert g['b.is'] == 'nested'
->>> assert g['c.a'] == [1, 2, 3]
->>>
->>> for k, v in g.items():
-...     print(f"{k}: {v}")
-...
-a: simple
-b.is: nested
-b: {'is': 'nested'}
-c.is: nested
-c.and: has
-c.a: [1, 2, 3]
-c: {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]}
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/mg_selectors.html b/docs/module_docs/py2store/utils/mg_selectors.html deleted file mode 100644 index 04a064e..0000000 --- a/docs/module_docs/py2store/utils/mg_selectors.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.utils.mg_selectors — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.mg_selectors

-

Selectors that use the mongo-query interface

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/mongoquery.html b/docs/module_docs/py2store/utils/mongoquery.html deleted file mode 100644 index 97f9eee..0000000 --- a/docs/module_docs/py2store/utils/mongoquery.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - - - - py2store.utils.mongoquery — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.mongoquery

-

Transform mongo-like selector dicts (filters) into boolean functions that implement the condition

-

Modified from mongoquery (https://github.com/kapouille/mongoquery)

-

mongoquery provides a straightforward API to match Python objects against -MongoDB Query Language queries.

-
-
-class py2store.utils.mongoquery.Query(definition)[source]
-

The Query class is used to match an object against a MongoDB-like query

-
-
-match(entry)[source]
-

Matches the entry object against the query specified on instanciation

-
- -
- -
-
-exception py2store.utils.mongoquery.QueryError[source]
-

Query error exception

-
- -
-
-py2store.utils.mongoquery.is_non_string_sequence(entry)[source]
-

Returns True if entry is a Python sequence iterable, and not a string

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/signatures.html b/docs/module_docs/py2store/utils/signatures.html deleted file mode 100644 index b7c54c1..0000000 --- a/docs/module_docs/py2store/utils/signatures.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - py2store.utils.signatures — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.signatures

-

Deprecated: Forwards to py2store.signatures

-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/sliceable.html b/docs/module_docs/py2store/utils/sliceable.html deleted file mode 100644 index e48a2dd..0000000 --- a/docs/module_docs/py2store/utils/sliceable.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - py2store.utils.sliceable — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.sliceable

-

utils to add sliceable functionality to stores

-
-
-class py2store.utils.sliceable.iSliceStore(store)[source]
-

Wraps a store to make a reader that acts as if the store was a list (with integer keys, and that can be sliced). -I say “list”, but it should be noted that the behavior is more that of range, that outputs an element of the list -when keying with an integer, but returns an iterable object (a range) if sliced.

-

Here, a map object is returned when the sliceable store is sliced.

-
>>> s = {'foo': 'bar', 'hello': 'world', 'alice': 'bob'}
->>> sliceable_s = iSliceStore(s)
->>> sliceable_s[1]
-'world'
->>> list(sliceable_s[0:2])
-['bar', 'world']
->>> list(sliceable_s[-2:])
-['world', 'bob']
->>> list(sliceable_s[:-1])
-['bar', 'world']
-
-
-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/timeseries_caching.html b/docs/module_docs/py2store/utils/timeseries_caching.html deleted file mode 100644 index d0a564c..0000000 --- a/docs/module_docs/py2store/utils/timeseries_caching.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - - py2store.utils.timeseries_caching — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.timeseries_caching

-

Tools to cache time-series data.

-
-
-class py2store.utils.timeseries_caching.RegularTimeseriesCache(data_rate=1, time_rate=1, maxlen=None)[source]
-

A type that pretends to be a (possibly very large) list, but where contents of the list are populated as they are -needed. Further, the indexing of the list can be overwritten for the convenience of the user.

-

The canonical application is where we have segments of continuous waveform indexed by utc microseconds timestamps.

-

It is convenient to be able to read segments of this waveform as if it was one big waveform (handling the -discontinuities gracefully), and have the choice of using (relative or absolute) integer indices or utc indices.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/module_docs/py2store/utils/uri_utils.html b/docs/module_docs/py2store/utils/uri_utils.html deleted file mode 100644 index 072f27e..0000000 --- a/docs/module_docs/py2store/utils/uri_utils.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - py2store.utils.uri_utils — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.utils.uri_utils

-

utils to work with URIs

-
-
-py2store.utils.uri_utils.build_uri(scheme, database='', username=None, password=None, host='localhost', port=None)[source]
-

Reverse of parse_uri function. -Builds a URI string from provided params.

-
- -
-
-py2store.utils.uri_utils.parse_uri(uri)[source]
-

Parses DB URI string into a dict of params. -:param uri: string formatted as: “scheme://username:password@host:port/database” -:return: a dict with these params parsed.

-
- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv deleted file mode 100644 index 8f9da2b..0000000 Binary files a/docs/objects.inv and /dev/null differ diff --git a/docs/py-modindex.html b/docs/py-modindex.html deleted file mode 100644 index a0b4477..0000000 --- a/docs/py-modindex.html +++ /dev/null @@ -1,588 +0,0 @@ - - - - - - - - Python Module Index — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Python Module Index

- -
- p -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- p
- py2store -
    - py2store.__init__ -
    - py2store.access -
    - py2store.appendable -
    - py2store.base -
    - py2store.caching -
    - py2store.core -
    - py2store.dig -
    - py2store.errors -
    - py2store.examples -
    - py2store.examples.dropbox_w_urllib -
    - py2store.examples.kv_walking -
    - py2store.examples.last_key_inserted -
    - py2store.examples.python_code_stats -
    - py2store.examples.write_caches -
    - py2store.ext -
    - py2store.ext.dataframes -
    - py2store.ext.docx -
    - py2store.ext.github -
    - py2store.ext.gitlab -
    - py2store.ext.hdf -
    - py2store.ext.matlab -
    - py2store.ext.wordnet -
    - py2store.filesys -
    - py2store.key_mappers -
    - py2store.key_mappers.naming -
    - py2store.key_mappers.paths -
    - py2store.key_mappers.str_utils -
    - py2store.key_mappers.tuples -
    - py2store.misc -
    - py2store.mixins -
    - py2store.my -
    - py2store.my.grabbers -
    - py2store.naming -
    - py2store.parse_format -
    - py2store.paths -
    - py2store.persisters -
    - py2store.persisters.dropbox_w_dropbox -
    - py2store.persisters.googledrive_w_pydrive -
    - py2store.persisters.local_files -
    - py2store.persisters.new_s3 -
    - py2store.persisters.redis_w_redis -
    - py2store.persisters.s3_w_boto3 -
    - py2store.persisters.sql_w_sqlalchemy -
    - py2store.persisters.w_aiofile -
    - py2store.serializers -
    - py2store.serializers.pickled -
    - py2store.signatures -
    - py2store.slib -
    - py2store.slib.s_configparser -
    - py2store.slib.s_zipfile -
    - py2store.sources -
    - py2store.stores -
    - py2store.stores.dropbox_store -
    - py2store.stores.local_store -
    - py2store.stores.s3_store -
    - py2store.stores.sql_w_sqlalchemy -
    - py2store.test -
    - py2store.test.local_files_test -
    - py2store.test.quick_test -
    - py2store.test.scrap -
    - py2store.test.util -
    - py2store.trans -
    - py2store.util -
    - py2store.utils -
    - py2store.utils.affine_conversion -
    - py2store.utils.appendable -
    - py2store.utils.attr_dict -
    - py2store.utils.cache_descriptors -
    - py2store.utils.cumul_aggreg_write -
    - py2store.utils.explicit -
    - py2store.utils.glom -
    - py2store.utils.mappify -
    - py2store.utils.mg_selectors -
    - py2store.utils.mongoquery -
    - py2store.utils.signatures -
    - py2store.utils.sliceable -
    - py2store.utils.timeseries_caching -
    - py2store.utils.uri_utils -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/search.html b/docs/search.html deleted file mode 100644 index dede3fd..0000000 --- a/docs/search.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - Search — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Search

-
- -

- Please activate JavaScript to enable the search - functionality. -

-
-

- Searching for multiple words only shows matches that contain - all words. -

-
- - - -
- -
- -
- -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js deleted file mode 100644 index b71aa26..0000000 --- a/docs/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["how_to","http_docs","index","mockobjects","module_docs/py2store","module_docs/py2store/access","module_docs/py2store/appendable","module_docs/py2store/base","module_docs/py2store/caching","module_docs/py2store/core","module_docs/py2store/dig","module_docs/py2store/errors","module_docs/py2store/examples","module_docs/py2store/examples/code_navig","module_docs/py2store/examples/dropbox_w_urllib","module_docs/py2store/examples/kv_walking","module_docs/py2store/examples/last_key_inserted","module_docs/py2store/examples/python_code_stats","module_docs/py2store/examples/write_caches","module_docs/py2store/ext","module_docs/py2store/ext/audio","module_docs/py2store/ext/dataframes","module_docs/py2store/ext/docx","module_docs/py2store/ext/github","module_docs/py2store/ext/gitlab","module_docs/py2store/ext/hdf","module_docs/py2store/ext/kaggle","module_docs/py2store/ext/matlab","module_docs/py2store/ext/module_imports","module_docs/py2store/ext/wordnet","module_docs/py2store/filesys","module_docs/py2store/key_mappers","module_docs/py2store/key_mappers/naming","module_docs/py2store/key_mappers/paths","module_docs/py2store/key_mappers/str_utils","module_docs/py2store/key_mappers/tuples","module_docs/py2store/misc","module_docs/py2store/mixins","module_docs/py2store/my","module_docs/py2store/my/grabbers","module_docs/py2store/naming","module_docs/py2store/parse_format","module_docs/py2store/paths","module_docs/py2store/persisters","module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress","module_docs/py2store/persisters/arangodb_w_pyarango","module_docs/py2store/persisters/couchdb_w_couchdb","module_docs/py2store/persisters/dropbox_w_dropbox","module_docs/py2store/persisters/dropbox_w_requests","module_docs/py2store/persisters/dropbox_w_urllib","module_docs/py2store/persisters/dynamodb_w_boto3","module_docs/py2store/persisters/ftp_persister","module_docs/py2store/persisters/googledrive_w_pydrive","module_docs/py2store/persisters/local_files","module_docs/py2store/persisters/mongo_w_pymongo","module_docs/py2store/persisters/new_s3","module_docs/py2store/persisters/redis_w_redis","module_docs/py2store/persisters/s3_w_boto3","module_docs/py2store/persisters/sql_w_odbc","module_docs/py2store/persisters/sql_w_sqlalchemy","module_docs/py2store/persisters/ssh_persister","module_docs/py2store/persisters/w_aiofile","module_docs/py2store/scrap/new_gen_local","module_docs/py2store/serializers","module_docs/py2store/serializers/audio","module_docs/py2store/serializers/jsonization","module_docs/py2store/serializers/pickled","module_docs/py2store/serializers/regular_panel_data","module_docs/py2store/serializers/sequential","module_docs/py2store/signatures","module_docs/py2store/slib","module_docs/py2store/slib/s_configparser","module_docs/py2store/slib/s_zipfile","module_docs/py2store/sources","module_docs/py2store/stores","module_docs/py2store/stores/arangodb_store","module_docs/py2store/stores/couchdb_store","module_docs/py2store/stores/delegation_stores","module_docs/py2store/stores/dropbox_store","module_docs/py2store/stores/local_store","module_docs/py2store/stores/mongo_store","module_docs/py2store/stores/s3_store","module_docs/py2store/stores/sql_w_sqlalchemy","module_docs/py2store/test","module_docs/py2store/test/local_files_test","module_docs/py2store/test/quick_test","module_docs/py2store/test/scrap","module_docs/py2store/test/simple_test","module_docs/py2store/test/trans_test","module_docs/py2store/test/util","module_docs/py2store/trans","module_docs/py2store/util","module_docs/py2store/utils","module_docs/py2store/utils/affine_conversion","module_docs/py2store/utils/appendable","module_docs/py2store/utils/attr_dict","module_docs/py2store/utils/cache_descriptors","module_docs/py2store/utils/cumul_aggreg_write","module_docs/py2store/utils/explicit","module_docs/py2store/utils/glom","module_docs/py2store/utils/mappify","module_docs/py2store/utils/mg_selectors","module_docs/py2store/utils/mongoquery","module_docs/py2store/utils/signatures","module_docs/py2store/utils/sliceable","module_docs/py2store/utils/timeseries_caching","module_docs/py2store/utils/uri_utils","table_of_contents","test"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.todo":2,"sphinx.ext.viewcode":1,sphinx:56},filenames:["how_to.md","http_docs.rst","index.rst","mockobjects.rst","module_docs/py2store.rst","module_docs/py2store/access.rst","module_docs/py2store/appendable.rst","module_docs/py2store/base.rst","module_docs/py2store/caching.rst","module_docs/py2store/core.rst","module_docs/py2store/dig.rst","module_docs/py2store/errors.rst","module_docs/py2store/examples.rst","module_docs/py2store/examples/code_navig.rst","module_docs/py2store/examples/dropbox_w_urllib.rst","module_docs/py2store/examples/kv_walking.rst","module_docs/py2store/examples/last_key_inserted.rst","module_docs/py2store/examples/python_code_stats.rst","module_docs/py2store/examples/write_caches.rst","module_docs/py2store/ext.rst","module_docs/py2store/ext/audio.rst","module_docs/py2store/ext/dataframes.rst","module_docs/py2store/ext/docx.rst","module_docs/py2store/ext/github.rst","module_docs/py2store/ext/gitlab.rst","module_docs/py2store/ext/hdf.rst","module_docs/py2store/ext/kaggle.rst","module_docs/py2store/ext/matlab.rst","module_docs/py2store/ext/module_imports.rst","module_docs/py2store/ext/wordnet.rst","module_docs/py2store/filesys.rst","module_docs/py2store/key_mappers.rst","module_docs/py2store/key_mappers/naming.rst","module_docs/py2store/key_mappers/paths.rst","module_docs/py2store/key_mappers/str_utils.rst","module_docs/py2store/key_mappers/tuples.rst","module_docs/py2store/misc.rst","module_docs/py2store/mixins.rst","module_docs/py2store/my.rst","module_docs/py2store/my/grabbers.rst","module_docs/py2store/naming.rst","module_docs/py2store/parse_format.rst","module_docs/py2store/paths.rst","module_docs/py2store/persisters.rst","module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst","module_docs/py2store/persisters/arangodb_w_pyarango.rst","module_docs/py2store/persisters/couchdb_w_couchdb.rst","module_docs/py2store/persisters/dropbox_w_dropbox.rst","module_docs/py2store/persisters/dropbox_w_requests.rst","module_docs/py2store/persisters/dropbox_w_urllib.rst","module_docs/py2store/persisters/dynamodb_w_boto3.rst","module_docs/py2store/persisters/ftp_persister.rst","module_docs/py2store/persisters/googledrive_w_pydrive.rst","module_docs/py2store/persisters/local_files.rst","module_docs/py2store/persisters/mongo_w_pymongo.rst","module_docs/py2store/persisters/new_s3.rst","module_docs/py2store/persisters/redis_w_redis.rst","module_docs/py2store/persisters/s3_w_boto3.rst","module_docs/py2store/persisters/sql_w_odbc.rst","module_docs/py2store/persisters/sql_w_sqlalchemy.rst","module_docs/py2store/persisters/ssh_persister.rst","module_docs/py2store/persisters/w_aiofile.rst","module_docs/py2store/scrap/new_gen_local.rst","module_docs/py2store/serializers.rst","module_docs/py2store/serializers/audio.rst","module_docs/py2store/serializers/jsonization.rst","module_docs/py2store/serializers/pickled.rst","module_docs/py2store/serializers/regular_panel_data.rst","module_docs/py2store/serializers/sequential.rst","module_docs/py2store/signatures.rst","module_docs/py2store/slib.rst","module_docs/py2store/slib/s_configparser.rst","module_docs/py2store/slib/s_zipfile.rst","module_docs/py2store/sources.rst","module_docs/py2store/stores.rst","module_docs/py2store/stores/arangodb_store.rst","module_docs/py2store/stores/couchdb_store.rst","module_docs/py2store/stores/delegation_stores.rst","module_docs/py2store/stores/dropbox_store.rst","module_docs/py2store/stores/local_store.rst","module_docs/py2store/stores/mongo_store.rst","module_docs/py2store/stores/s3_store.rst","module_docs/py2store/stores/sql_w_sqlalchemy.rst","module_docs/py2store/test.rst","module_docs/py2store/test/local_files_test.rst","module_docs/py2store/test/quick_test.rst","module_docs/py2store/test/scrap.rst","module_docs/py2store/test/simple_test.rst","module_docs/py2store/test/trans_test.rst","module_docs/py2store/test/util.rst","module_docs/py2store/trans.rst","module_docs/py2store/util.rst","module_docs/py2store/utils.rst","module_docs/py2store/utils/affine_conversion.rst","module_docs/py2store/utils/appendable.rst","module_docs/py2store/utils/attr_dict.rst","module_docs/py2store/utils/cache_descriptors.rst","module_docs/py2store/utils/cumul_aggreg_write.rst","module_docs/py2store/utils/explicit.rst","module_docs/py2store/utils/glom.rst","module_docs/py2store/utils/mappify.rst","module_docs/py2store/utils/mg_selectors.rst","module_docs/py2store/utils/mongoquery.rst","module_docs/py2store/utils/signatures.rst","module_docs/py2store/utils/sliceable.rst","module_docs/py2store/utils/timeseries_caching.rst","module_docs/py2store/utils/uri_utils.rst","table_of_contents.rst","test.rst"],objects:{"":{py2store:[4,0,0,"-"]},"py2store.__init__":{ihead:[108,1,1,""],kvhead:[108,1,1,""]},"py2store.access":{compose:[108,1,1,""],dflt_func_loader:[108,1,1,""],dotpath_to_func:[108,1,1,""],dotpath_to_obj:[108,1,1,""],fakit:[108,1,1,""],getenv:[108,1,1,""]},"py2store.examples":{dropbox_w_urllib:[14,0,0,"-"],kv_walking:[108,0,0,"-"],last_key_inserted:[16,0,0,"-"],python_code_stats:[108,0,0,"-"],write_caches:[108,0,0,"-"]},"py2store.examples.dropbox_w_urllib":{DropboxFileCopyReader:[14,2,1,""],DropboxFolderCopyReader:[14,2,1,""]},"py2store.examples.kv_walking":{SrcReader:[108,2,1,""],conjunction:[108,1,1,""],kv_walk:[108,1,1,""]},"py2store.examples.kv_walking.SrcReader":{update_keys_cache:[108,3,1,""]},"py2store.examples.last_key_inserted":{remember_last_key_written_to:[16,1,1,""]},"py2store.examples.write_caches":{timestamp_on_cache_and_concatenate_all_values:[108,1,1,""]},"py2store.ext":{dataframes:[108,0,0,"-"],docx:[108,0,0,"-"],github:[108,0,0,"-"],gitlab:[108,0,0,"-"],hdf:[108,0,0,"-"],matlab:[108,0,0,"-"],wordnet:[29,0,0,"-"]},"py2store.key_mappers":{naming:[108,0,0,"-"],paths:[108,0,0,"-"],str_utils:[108,0,0,"-"],tuples:[108,0,0,"-"]},"py2store.key_mappers.str_utils":{args_and_kwargs_indices:[108,1,1,""],auto_field_format_str:[108,1,1,""],compile_str_from_parsed:[108,1,1,""],format_params_in_str_format:[108,1,1,""],get_explicit_positions:[108,1,1,""],is_automatic_format_params:[108,1,1,""],is_automatic_format_string:[108,1,1,""],is_hybrid_format_params:[108,1,1,""],is_hybrid_format_string:[108,1,1,""],is_manual_format_params:[108,1,1,""],is_manual_format_string:[108,1,1,""],manual_field_format_str:[108,1,1,""],n_format_params_in_str_format:[108,1,1,""],name_fields_in_format_str:[108,1,1,""]},"py2store.key_mappers.tuples":{dsv_of_list:[108,1,1,""],list_of_dsv:[108,1,1,""],mk_obj_of_str:[108,1,1,""],mk_str_of_obj:[108,1,1,""],str_of_tuple:[108,1,1,""]},"py2store.misc":{MiscGetter:[108,2,1,""],MiscGetterAndSetter:[108,2,1,""],MiscReaderMixin:[108,2,1,""],MiscStoreMixin:[108,2,1,""],get_obj:[108,1,1,""],set_obj:[108,1,1,""]},"py2store.my":{grabbers:[108,0,0,"-"]},"py2store.parse_format":{findall:[108,1,1,""],parse:[108,1,1,""],search:[108,1,1,""],with_pattern:[108,1,1,""]},"py2store.persisters":{dropbox_w_dropbox:[108,0,0,"-"],googledrive_w_pydrive:[108,0,0,"-"],local_files:[108,0,0,"-"],new_s3:[108,0,0,"-"],redis_w_redis:[108,0,0,"-"],s3_w_boto3:[108,0,0,"-"],sql_w_sqlalchemy:[108,0,0,"-"],w_aiofile:[108,0,0,"-"]},"py2store.persisters.local_files":{DirReader:[108,2,1,""],DirpathFormatKeys:[108,2,1,""],FileReader:[108,2,1,""],FilepathFormatKeys:[108,2,1,""],FolderNotFoundError:[108,4,1,""],LocalFileRWD:[108,2,1,""],LocalFileStreamGetter:[108,2,1,""],PathFormatPersister:[108,2,1,""],PrefixedDirpathsRecursive:[108,2,1,""],PrefixedFilepaths:[108,2,1,""],PrefixedFilepathsRecursive:[108,2,1,""],ensure_slash_suffix:[108,1,1,""]},"py2store.serializers":{pickled:[108,0,0,"-"]},"py2store.serializers.pickled":{mk_marshal_rw_funcs:[108,1,1,""],mk_pickle_rw_funcs:[108,1,1,""]},"py2store.slib":{s_configparser:[108,0,0,"-"],s_zipfile:[108,0,0,"-"]},"py2store.slib.s_zipfile":{EmptyZipError:[108,4,1,""],FileStreamsOfZip:[108,2,1,""],FilesOfZip:[108,2,1,""],FlatZipFilesReader:[108,2,1,""],OverwriteNotAllowed:[108,4,1,""],ZipFileReader:[108,5,1,""],ZipFileStreamsReader:[108,2,1,""],ZipFilesReader:[108,2,1,""],ZipFilesReaderAndBytesWriter:[108,2,1,""],ZipReader:[108,2,1,""],ZipStore:[108,2,1,""],func_conjunction:[108,1,1,""],mk_flatzips_store:[108,1,1,""]},"py2store.stores":{dropbox_store:[108,0,0,"-"],local_store:[108,0,0,"-"],s3_store:[108,0,0,"-"],sql_w_sqlalchemy:[108,0,0,"-"]},"py2store.stores.local_store":{AutoMkDirsOnSetitemMixin:[108,2,1,""],AutoMkPathformatMixin:[108,2,1,""],DirStore:[108,2,1,""],LocalBinaryStore:[108,2,1,""],LocalJsonStore:[108,2,1,""],LocalPickleStore:[108,2,1,""],LocalStore:[108,5,1,""],LocalTextStore:[108,2,1,""],MakeMissingDirsStoreMixin:[108,2,1,""],PathFormatStore:[108,2,1,""],PathFormatStoreWithPrefix:[108,2,1,""],PickleStore:[108,5,1,""],QuickBinaryStore:[108,2,1,""],QuickJsonStore:[108,2,1,""],QuickLocalStoreMixin:[108,2,1,""],QuickPickleStore:[108,2,1,""],QuickStore:[108,5,1,""],QuickTextStore:[108,2,1,""],RelativeDirPathFormatKeys:[108,2,1,""],RelativePathFormatStore2:[108,2,1,""]},"py2store.test":{local_files_test:[84,0,0,"-"],quick_test:[85,0,0,"-"],scrap:[108,0,0,"-"],util:[108,0,0,"-"]},"py2store.test.util":{random_dict_gen:[108,1,1,""],random_formatted_str_gen:[108,1,1,""],random_string:[108,1,1,""],random_tuple_gen:[108,1,1,""],random_word:[108,1,1,""],random_word_gen:[108,1,1,""]},"py2store.utils":{affine_conversion:[108,0,0,"-"],appendable:[108,0,0,"-"],attr_dict:[95,0,0,"-"],cache_descriptors:[108,0,0,"-"],cumul_aggreg_write:[108,0,0,"-"],explicit:[108,0,0,"-"],glom:[108,0,0,"-"],mappify:[108,0,0,"-"],mg_selectors:[101,0,0,"-"],mongoquery:[102,0,0,"-"],signatures:[108,0,0,"-"],sliceable:[108,0,0,"-"],timeseries_caching:[108,0,0,"-"],uri_utils:[108,0,0,"-"]},"py2store.utils.affine_conversion":{AffineConverter:[108,2,1,""],get_affine_converter_and_inverse:[108,1,1,""]},"py2store.utils.attr_dict":{AttrMap:[95,2,1,""],attr_wrap:[95,1,1,""],iskeyword:[95,1,1,""]},"py2store.utils.cache_descriptors":{CachedProperty:[108,1,1,""],Lazy:[108,2,1,""],cachedIn:[108,2,1,""]},"py2store.utils.cumul_aggreg_write":{CumulAggregWrite:[108,2,1,""],CumulAggregWriteKvItems:[108,2,1,""],CumulAggregWriteWithAutoFlush:[108,2,1,""],condition_flush_on_every_write:[108,1,1,""],mk_group_aggregator:[108,1,1,""],mk_group_aggregator_with_key_func:[108,1,1,""]},"py2store.utils.explicit":{ExplicitKeymapReader:[108,2,1,""],ExplicitKeys:[108,2,1,""],ExplicitKeysSource:[108,2,1,""],ExplicitKeysStore:[108,2,1,""],ExplicitKeysWithPrefixRelativization:[108,2,1,""],ObjReader:[108,2,1,""],invertible_maps:[108,1,1,""]},"py2store.utils.glom":{Auto:[108,2,1,""],Call:[108,2,1,""],Check:[108,2,1,""],CheckError:[108,4,1,""],Coalesce:[108,2,1,""],CoalesceError:[108,4,1,""],Fill:[108,2,1,""],GlomError:[108,4,1,""],Glommer:[108,2,1,""],Inspect:[108,2,1,""],Invoke:[108,2,1,""],Let:[108,2,1,""],Literal:[108,2,1,""],Path:[108,2,1,""],PathAccessError:[108,4,1,""],Spec:[108,2,1,""],TType:[108,2,1,""],TargetRegistry:[108,2,1,""],UnregisteredTarget:[108,4,1,""],glom:[108,1,1,""],is_iterable:[108,1,1,""],make_sentinel:[108,1,1,""],register:[108,1,1,""],register_op:[108,1,1,""]},"py2store.utils.glom.Call":{glomit:[108,3,1,""]},"py2store.utils.glom.Glommer":{register:[108,3,1,""]},"py2store.utils.glom.Invoke":{constants:[108,3,1,""],specfunc:[108,3,1,""],specs:[108,3,1,""],star:[108,3,1,""]},"py2store.utils.glom.Path":{from_t:[108,3,1,""],from_text:[108,3,1,""],items:[108,3,1,""],values:[108,3,1,""]},"py2store.utils.glom.TargetRegistry":{get_handler:[108,3,1,""],register_op:[108,3,1,""]},"py2store.utils.mappify":{LeafMappify:[108,2,1,""],Mappify:[108,2,1,""]},"py2store.utils.mongoquery":{Query:[102,2,1,""],QueryError:[102,4,1,""],is_non_string_sequence:[102,1,1,""]},"py2store.utils.mongoquery.Query":{match:[102,3,1,""]},"py2store.utils.sliceable":{iSliceStore:[108,2,1,""]},"py2store.utils.timeseries_caching":{RegularTimeseriesCache:[108,2,1,""]},"py2store.utils.uri_utils":{build_uri:[108,1,1,""],parse_uri:[108,1,1,""]},py2store:{__init__:[108,0,0,"-"],access:[108,0,0,"-"],appendable:[6,0,0,"-"],base:[108,0,0,"-"],caching:[108,0,0,"-"],core:[108,0,0,"-"],dig:[108,0,0,"-"],errors:[108,0,0,"-"],examples:[108,0,0,"-"],ext:[108,0,0,"-"],filesys:[108,0,0,"-"],ihead:[4,1,1,""],key_mappers:[108,0,0,"-"],kvhead:[4,1,1,""],misc:[108,0,0,"-"],mixins:[108,0,0,"-"],my:[108,0,0,"-"],naming:[40,0,0,"-"],parse_format:[108,0,0,"-"],paths:[42,0,0,"-"],persisters:[108,0,0,"-"],serializers:[108,0,0,"-"],signatures:[69,0,0,"-"],slib:[108,0,0,"-"],sources:[108,0,0,"-"],stores:[108,0,0,"-"],test:[108,0,0,"-"],trans:[108,0,0,"-"],util:[108,0,0,"-"],utils:[108,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","method","Python method"],"4":["py","exception","Python exception"],"5":["py","attribute","Python attribute"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:method","4":"py:exception","5":"py:attribute"},terms:{"0000":[41,108],"000000":[17,108],"02f":[34,108],"038432":[17,108],"0440":[41,108],"057210":[17,108],"074142":[17,108],"0x1538999e0":[72,108],"100":[41,89,99,108],"1000":[41,89,108],"105":[89,108],"108150":[17,108],"1215":[36,108],"1273":[17,108],"1276":[17,108],"1301":[17,108],"133":[17,108],"136818":[17,108],"138":[17,108],"1415":16,"14159":[99,108],"148903":[17,108],"155":[98,108],"157034":[17,108],"1574287049078391":[72,108],"1574288084739961":[72,108],"1574304926795633":[72,108],"1574305026895702":[72,108],"1574305159343326":[72,108],"1574305276853053":[72,108],"1574333557263758":[72,108],"178":[17,108],"189":[17,108],"1893":0,"190":[17,108],"19430710":0,"1956":[36,108],"1972":[41,108],"1973":[41,108],"1983":[35,108],"2011":[41,108],"2012":[41,108],"2017":[41,108],"2018":[99,108],"2019_11_21":[72,108],"20t10":[41,108],"213907":[17,108],"218":[17,108],"2822":[41,108],"321cba":[36,108],"322":[17,108],"3432":[89,108],"36z":[41,108],"4213":[89,108],"425":[17,108],"4343":[17,108],"449654":[17,108],"463768":[17,108],"53280":[72,108],"53432":[72,108],"585":[17,108],"600":[89,108],"682":[17,108],"7000":[89,108],"785714":[17,108],"8601":[41,108],"929":[17,108],"boolean":[97,102,108],"byte":[0,7,36,53,72,108],"case":[0,36,41,72,97,99,108],"catch":[99,108],"class":[0,5,6,7,14,15,16,35,36,53,72,79,93,95,96,97,98,99,100,102,104,105,108],"default":[0,5,7,14,36,41,72,79,99,108],"export":[41,108],"fa\u00e7ad":95,"float":[36,41,108],"function":[5,6,15,35,36,38,39,41,53,66,72,79,84,89,93,94,97,98,99,100,102,104,106,108],"import":[0,17,19,29,35,36,41,53,72,79,98,99,108],"int":[7,35,36,41,53,79,89,93,99,108],"long":[0,79,93,108],"new":[15,16,36,79,97,99,108],"return":[5,7,16,34,35,41,72,79,89,93,95,97,98,99,102,104,106,108],"short":[99,108],"static":[72,108],"switch":[99,108],"true":[16,34,41,66,72,79,98,99,102,108],"try":[79,99,108],"var":[5,108],"while":[99,108],AND:[53,99,108],ARE:[99,108],Age:[41,108],And:[41,99,108],BUT:[99,108],But:[0,7,72,99,108],FOR:[99,108],For:[7,41,72,99,108],Its:[99,108],NOT:[95,99,108],Not:[89,108],One:[5,72,99,108],SUCH:[99,108],Such:[99,108],THE:[99,108],THe:[7,108],That:[0,7,19,35,41,66,108],The:[0,5,6,7,15,16,18,29,34,35,41,53,72,89,93,97,98,99,102,105,108],There:[5,41,108],These:[7,19,99,108],USE:[99,108],Use:[16,99,108],Using:[41,108],Will:[79,108],With:0,__contains__:[53,95,108],__file__:[79,98,108],__getitem__:[7,41,108],__init__:[17,36,99],__iter__:[53,108],__len__:[53,108],__macosx:[72,108],__setitem__:[7,108],_data_of_obj:[7,108],_id_of_kei:[7,108],_incoming_val_trans_for_kei:[36,108],_key_of_id:[7,108],_keys_cach:[15,108],_last_key_written_to:16,_miss:[99,108],_obj_of_data:[7,108],_obj_of_kei:[98,108],_outgoing_val_trans_for_kei:[36,108],_prefix:[53,79,98,108],_rootdir:[79,108],a_fil:0,a_special_attr:95,a_subfold:0,aaa:[89,108],aaa_aaa:[89,108],aab:[89,108],aacc:[89,108],abb_cbab:[89,108],abbrevi:[41,108],abc123:[36,108],abc:[7,15,17,89,98,108],abcb:[89,108],abcd:[89,108],abcdefghijklmnop:[97,108],abcdefghijklmnopqrstuvwxyz:[89,108],abil:[41,72,99,108],abl:[5,105,108],about:[0,17,24,40,41,72,99,108],abov:[72,99,108],absolut:[105,108],ac_cc:[89,108],aca:[89,108],acba:[89,108],accept:[41,108],access:[0,2,14,22,30,41,95,99,107],accessor:[19,99,108],accompani:[99,108],accord:[36,97,108],accordingli:[72,108],account:[100,108],accumul:[97,108],accur:[99,108],achiev:[99,108],acrobat:[72,108],act:[72,99,104,108],action:[99,108],actual:[7,36,41,108],adapt:[41,108],add:[6,7,8,16,19,36,41,53,79,89,94,97,99,104,108],add_append_functionality_to_store_cl:6,added:[41,108],adding:[89,108],addit:[41,99,108],adgjmp:[97,108],advanc:[99,108],advis:[72,99,108],affect:[99,108],affin:[93,108],affine_convers:[2,107],affine_convert:[93,108],affineconvert:[93,108],after:[41,108],against:[99,102,108],age:[97,108],aggreg:[97,108],aggregator_op:[97,108],aiofiledol:[61,108],airplan:[97,108],aka:[99,108],alex:[41,108],algorithm:[18,108],alia:[72,79,108],alic:[41,98,104,108],align:[41,108],all:[0,5,7,15,24,34,41,43,72,97,99,108],alloc:[41,108],allow:[5,7,41,93,108],allow_overwrit:[72,108],almost:[99,108],along:[5,72,108],alphabet:[89,108],alreadi:[16,53,99,108],also:[6,7,34,36,41,93,99,108],alwai:[0,41,108],ambigu:[99,108],amongst:0,amount:[41,108],ani:[5,35,41,72,96,99,100,108],annoi:[7,108],annot:[41,108],anonym:[41,108],anoth:[53,72,79,99,108],another_attr:95,answer:[41,108],anytim:[97,108],anywher:[41,99,108],api:[99,102,108],app:[72,108],appear:[34,99,108],append:[2,18,53,99,107],appendable_store_cl:6,appendable_stream:[53,108],appl:[97,108],appli:[72,93,97,99,108],applic:[41,99,105,108],appropri:[72,108],arg:[15,36,79,95,96,99,108],args_and_kwargs_indic:[34,108],argument:[72,99,108],aris:[99,108],aros:[99,108],around:[29,99,108],arrai:[36,108],ascii:[66,79,97,108],asi:[15,108],asid:[99,108],ask:[72,108],assert:[16,34,36,41,66,79,89,95,99,100,108],assertionerror:[98,108],assign:[99,108],associ:[99,108],assum:[0,72,79,99,108],attach:[41,108],attempt:[41,108],attr:[35,95,99,108],attr_dict:[2,107],attr_wrap:95,attribut:[35,41,95,96,99,108],attribute_nam:[96,108],attributeerror:[99,108],attrmap:95,audio:[36,72],author:[99,108],auto:[34,35,79,99,108],auto_field_format_str:[34,35,108],auto_func:[99,108],automat:[34,36,41,79,89,99,108],automkdirsonsetitemmixin:[79,108],automkpathformatmixin:[79,108],avail:[41,93,108],avoid:[99,108],back:[7,33,98,108],backend:[53,108],bananna:[97,108],bar:[35,79,95,98,104,108],bare:[41,108],base:[2,41,43,53,95,98,99,107],base_url:[24,108],basic:[41,93,99,108],bb_abc:[89,108],beautifulli:[99,108],becaus:[89,99,108],becom:[99,108],been:[41,72,108],befor:[34,36,79,99,108],begin:[99,108],behavior:[99,104,108],behaviour:[41,108],behkn:[97,108],being:[89,99,108],believ:[36,108],below:[41,99,108],bend:[99,108],better:[41,99,108],between:[41,108],beyond:[99,108],big:[99,105,108],bin:[36,108],binari:[41,79,89,97,99,108],blah:[79,108],blanklin:[17,108],bloo:[79,108],bob:[104,108],bold:[41,108],bolton:[99,108],book:95,bool:[15,72,99,108],born:[97,108],both:[5,15,34,41,72,98,99,108],box:[72,108],brace:[34,41,108],bracket:[41,108],branch:[24,108],breakpoint:[99,108],brief:[41,108],bring:[41,108],brown:[35,108],bug:[41,108],build:[19,106,108],build_uri:[106,108],built:[5,19,41,72,89,97,99,108],builtin:[14,19,41,99,108],bulk:[97,108],bunch:[72,108],busi:[99,108],cabba:[89,108],cach:[2,5,18,72,96,97,105,107],cache_descriptor:[2,107],cache_to_kv:[97,108],cachedin:[96,108],cachedproperti:[96,108],calculu:69,call:[6,15,98,99,108],callabl:[5,93,98,99,108],can:[0,5,7,19,29,34,36,41,53,72,99,104,105,108],canon:[105,108],care:[99,108],carri:[93,108],carta:[36,108],case_sensit:[41,108],cast:[93,108],categori:[72,108],caught:[99,108],caus:[41,99,108],cbbc_ca:[89,108],ccbb_ab:[89,108],center:[41,108],central:[29,99,108],certain:[99,108],cfilo:[97,108],chain:[99,108],chanc:[89,108],chang:0,charact:[5,41,97,98,108],check:[0,99,108],checkerror:[99,108],choic:[72,89,105,108],choos:[7,79,108],chr:[36,108],clariti:[99,108],classmethod:[99,108],clean:[99,108],cleanup:[41,108],clearli:[99,108],clock:[18,108],closest:[99,108],cls:[16,95],clue:[36,108],cnf:[36,108],coal_obj:[99,108],coalesc:[99,108],coalesceerror:[99,108],code:[16,41,86,93,99,108],collect:[7,15,17,53,97,98,108],collectiontyp:[98,108],column:[17,108],com:[41,102,108],combin:[89,99,108],come:[41,99,108],comma:[41,108],comment_lin:[17,108],comment_lines_ratio:[17,108],common:[0,29,41,99,108],commonli:[99,108],compar:[72,108],compat:[33,41,108],compil:[41,72,99,108],compile_str_from_pars:[34,108],complement:[99,108],complet:[41,108],complex:[41,66,93,108],complic:[99,108],compon:[35,108],compos:[5,99,108],composit:[5,99,108],compress:[36,72,108],concat_func:[89,108],concaten:[41,89,97,108],concatin:[97,108],concern:[7,108],concret:[79,108],condit:[97,99,102,108],condition_flush_on_every_writ:[97,108],conf:[36,108],config:[36,108],configpars:[71,108],configur:[5,7,38,108],conflict:[72,108],confus:[99,108],conjunct:[15,108],connector:[19,108],consequenti:[99,108],consid:[7,34,35,99,108],consist:[35,41,89,108],consol:29,constant:[99,108],constitut:[7,108],constraint:[89,108],construct:[29,99,108],constructor:[35,79,93,99,108],contain:[41,53,72,73,79,99,108],content:[0,2,36,41,72,79,98,105,107,108],contents_of_kei:[98,108],context:[99,108],contigu:[41,108],continu:[105,108],contract:[99,108],contributor:[99,108],control:[41,72,99,108],conveni:[41,72,105,108],convent:[72,99,108],convers:[2,34,35,93,107],convert:[7,35,41,93,108],copi:[14,99,108],copyright:[41,99,108],core:[2,99,107],corpu:29,correct:[41,108],correspond:[35,72,108],could:[5,34,99,108],count:[99,108],coupl:[41,99,108],cover:0,cowan:[41,108],creat:[16,79,99,108],credenti:[5,108],csv:[35,36,97,108],csv_fileobj:[36,108],ctime:[41,108],ctor:[72,108],cumul_aggreg_writ:[2,107],cumulaggregwrit:[97,108],cumulaggregwritekvitem:[97,108],cumulaggregwritewithautoflush:[97,108],current:[0,24,41,99,108],curri:[99,108],custom:[2,72,107],customiz:0,dai:[5,41,108],damag:[99,108],data:[0,4,5,7,19,21,23,25,27,36,39,41,71,72,79,95,96,98,99,105,108],data_r:[105,108],data_to_writ:[36,108],databas:[106,108],dataclass:[35,108],datadata:[79,108],datafram:[2,107],date:[41,108],datetim:[41,99,108],david:[41,108],deal:[36,108],dear:[35,108],debug:[99,108],decim:[41,108],declar:[99,108],decod:[36,72,108],decompress:[36,108],decor:[16,35,36,41,96,108],deep:[99,108],def:[16,36,41,79,96,98,99,108],default_factori:[99,108],defin:[39,41,89,98,99,108],definit:102,del:[36,108],delet:[53,79,108],delimit:[35,99,108],demo:[12,108],denot:[34,99,108],depend:[0,19,39,89,99,108],deprec:[32,103,108],deriv:[99,108],descriptor:[96,108],deseri:[7,36,108],deserialized_d:[66,108],design:[99,108],desir:[5,99,108],destruct:[53,108],detail:[7,34,99,108],dflt_func_load:[5,108],dflt_incoming_val_tran:[36,108],dflt_outgoing_val_tran:[36,108],dialect:[99,108],dict:[5,7,16,35,36,41,66,89,95,98,99,100,102,106,108],dictat:[99,108],dictionari:[41,108],did:[72,99,108],didn:[79,108],differ:[36,41,72,97,108],dig:[2,107],digit:[41,108],dir1:[41,108],dir2:[41,108],dir:[53,72,79,95,99,108],dir_of_zip:[72,108],direct:[0,99,108],directli:[41,53,96,108],directori:[53,72,79,108],dirnam:[79,108],dirpathformatkei:[53,108],dirread:[53,108],dirs_onli:[72,108],dirstor:[79,108],disclaim:[99,108],discontinu:[105,108],disguis:[72,108],dispar:[73,108],distinguish:[72,108],distribut:[99,108],doc:[6,22,41,72,89,99,108],docs_lin:[17,108],doctest:[29,36,89,108],document:[41,99,108],docx:[2,107],doe:[0,41,93,99,108],doesn:[99,108],dol:[6,7,8,9,10,11,30,37,40,42,69,73,90,91,108],don:[0,7,29,36,89,99,108],done:[41,108],dot:[41,99,108],dotpath:[5,108],dotpath_to_func:[5,108],dotpath_to_modul:[5,108],dotpath_to_obj:[5,108],doubl:[41,99,108],download:[29,72,108],draw:[89,108],drawn:[89,108],dropbox:14,dropbox_stor:[2,107],dropbox_w_dropbox:[2,107],dropbox_w_urllib:[2,107],dropboxdol:[47,78,108],dropboxfilecopyread:14,dropboxfoldercopyread:14,ds_store:[72,108],dsv:[35,108],dsv_of_list:[35,108],dtype:[17,108],duct:[99,108],dump:[36,66,79,108],duplic:[34,108],dure:[99,108],each:[41,93,97,98,99,108],easi:[99,108],easiest:29,easili:[5,108],echo:[99,108],edg:[41,108],edu:29,effect:[41,99,108],effici:[72,93,108],either:[41,99,108],element:[0,22,35,41,72,89,97,104,108],elimin:[99,108],els:[36,41,99,108],empti:[35,36,97,99,108],empty_lin:[17,108],empty_lines_ratio:[17,108],emptyziperror:[72,108],enabl:[99,108],encapsul:[99,108],encod:[36,108],encount:[72,99,108],end:[41,53,72,99,108],endors:[99,108],endpo:[41,108],enforc:[41,108],engel:[41,108],english:29,enhanc:[99,108],enough:[34,108],ensur:[41,108],ensure_slash_suffix:[53,108],entri:102,entrypoint:[99,108],env:[5,108],environ:[5,108],environment:[5,108],equal:[15,79,99,108],equal_to:[99,108],equival:[41,72,99,108],error:[2,41,99,102,107],essenti:[5,108],etc:[0,5,19,34,36,41,98,108],evalu:[41,99,108],evaluate_result:[41,108],even:[35,41,97,99,108],event:[36,99,108],ever:[15,99,108],everi:[5,18,97,99,108],everyth:[19,36,98,99,108],exact:[99,108],exactli:[41,108],exampl:[2,6,7,24,41,72,89,99,107],exampleclass:[99,108],exc:[99,108],excel:[99,108],except:[34,41,53,72,99,102,108],execut:[5,99,108],exemplari:[99,108],exist:[16,36,79,99,108],expand:[99,108],expandus:[36,108],expect:[16,98,99,108],experi:[99,108],experiment:[99,108],explicit:[2,41,99,107],explicitkei:[98,108],explicitkeymapread:[98,108],explicitkeyssourc:[98,108],explicitkeysstor:[98,108],explicitkeyswithprefixrelativ:[98,108],explicitli:[98,99,108],explor:[15,108],expon:[41,108],expos:[7,108],express:[34,41,99,108],ext:[2,107],extend:[6,94,108],extens:[19,36,39,79,99,108],extern:[5,35,108],extra_mk_store_kwarg:[72,108],extra_typ:[41,108],extract:[41,72,99,108],face:[41,108],factori:[16,35,108],fail:[99,108],failur:[99,108],fak:[5,108],fakit:[5,108],fallback:[99,108],fals:[16,34,41,72,79,98,99,108],familar:[99,108],fan:[99,108],far:[99,108],faster:[89,108],fault:[99,108],favorit:[72,108],featur:[99,108],feel:[99,108],few:[29,99,108],field:[34,35,36,40,41,89,99,108],field_nam:[34,108],file:[5,7,25,30,36,41,53,72,79,83,84,98,108],file_info_filt:[72,108],file_where_this_code_i:[98,108],fileinfo:[72,108],filenam:[72,108],filepath:[5,35,36,53,72,79,108],filepath_of:[79,108],filepath_of_2:[79,108],filepathformatkei:[53,108],fileread:[53,108],files_onli:[72,108],filesi:[2,107],filesofzip:[36,72,108],filestreamsofzip:[72,108],fill:[34,41,99,108],filt:[72,108],filt_it:[72,108],filter:[5,72,102,108],find:[0,41,72,98,108],findal:[41,108],first:[4,5,35,41,72,97,99,108],fit:[41,99,108],fix:[41,108],fix_import:[66,79,108],flag:[99,108],flatzip:0,flatzipfilesread:[72,108],flexibl:[34,41,108],float64:[17,108],fluent:95,flush_cache_condit:[97,108],folder:[0,7,14,72,79,108],foldernotfounderror:[53,108],follow:[0,7,35,41,89,99,108],foo:[34,35,79,95,98,104,108],food:[41,108],form:[5,15,99,108],format:[2,34,35,72,89,99,106,107],format_param:[34,108],format_params_in_str_format:[34,108],format_spec:[34,108],format_str:[34,89,108],formatt:[34,108],forward:[6,7,8,9,10,11,30,32,33,37,40,42,43,47,52,55,56,57,59,61,69,73,78,81,82,90,91,103,108],found:[41,99,108],fox:[35,108],fraction:[41,108],free:[5,108],from:[0,5,7,21,34,35,36,41,53,72,79,89,98,99,102,106,108],from_slope_and_intercept:[93,108],from_t:[99,108],from_text:[99,108],fulfil:[41,108],full:[0,14,41,53,79,99,108],fuller:[99,108],fullpath:[79,108],fullpath_of_relative_path:[79,108],func1:[72,108],func2:[72,108],func:[5,96,99,108],func_1:[15,108],func_conjunct:[72,108],func_kei:[36,108],func_load:[5,108],func_n:[15,108],func_nam:[5,108],function_lin:[17,108],function_lines_ratio:[17,108],functool:[97,99,108],furrer:[41,108],further:[7,15,93,99,105,108],fuss:29,futur:[99,108],gatewai:29,gen:[15,108],gener:[36,40,41,66,79,89,91,92,97,99,108],get:[0,4,5,6,7,16,17,24,29,34,36,41,53,72,79,89,93,98,99,108],get_affine_converter_and_invers:[93,108],get_branch:[24,108],get_branch_nam:[24,108],get_explicit_posit:[34,108],get_handl:[99,108],get_obj:[36,108],get_project_nam:[24,108],getattr:[99,108],getenv:[5,108],gettempdir:[79,108],github:[2,41,102,107],gitlab:[2,107],gitlabaccessor:[24,108],give:[0,6,14,39,41,99,108],given:[5,16,53,79,93,96,97,98,99,108],glad:[99,108],global:[41,89,99,108],glom:[2,100,107],glomcheckerror:[99,108],glomerror:[99,108],glomit:[99,108],glommabl:[99,108],glommer:[99,108],glomspec:[99,108],going:[36,41,72,108],good:[99,108],goodby:16,goodi:[4,108],googledrive_w_pydr:[2,107],got:[99,108],gotcha:[2,107],grabber:[2,107],gracefulli:[105,108],grail:[41,108],great:[99,108],greater:[41,108],grenad:[41,108],group:[41,97,108],guido:[36,108],gzip:[36,108],hand:[41,108],handi:[99,108],handl:[0,36,41,99,105,108],handler:[19,99,108],happili:[41,108],harder:[7,108],has:[0,5,6,34,35,41,72,99,100,108],hashemi:[99,108],have:[0,5,7,16,35,41,72,79,89,95,99,105,108],hdf:[2,107],head:[0,98,108],hello:[16,34,35,36,41,53,104,108],help:[0,5,99,108],her:[19,41,108],here:[0,5,29,34,35,36,99,100,104,108],hexadecim:[41,108],him:[19,108],histori:[41,108],hold:[41,108],holder:[99,108],holi:[41,108],honor:[41,108],hood:[79,108],hope:[41,108],host:[106,108],hour:[41,108],how:[5,7,16,19,36,41,72,89,99,108],howev:[99,108],howto:29,html:[29,72,108],http:[24,29,41,72,99,102,108],hybrid:[34,108],id_of_kei:[98,108],ident:[7,79,108],identifi:[41,95,108],identity_method:[36,108],ignor:[99,108],ignore_test:[72,108],ihead:[4,108],iif:[98,108],implement:[7,18,41,98,99,102,108],impli:[41,99,108],importantli:[7,108],importerror:[19,108],impos:[35,108],improv:[41,108],incident:[99,108],includ:[5,6,41,99,108],incom:[36,108],incoming_val_trans_for_kei:[36,108],index:[2,5,34,41,89,99,105,108],indexerror:[99,108],indic:[34,41,93,99,105,108],indirect:[99,108],inf:[53,79,108],inform:[19,24,41,108],ini:[36,108],initi:[41,97,98,108],inject:[34,108],input:[5,7,34,35,41,93,98,99,108],ins:[19,108],insensit:[41,108],insert:[16,18,99,108],insid:[34,99,108],inspect:[99,108],instal:[17,29,108],instanc:[41,72,98,99,108],instance_of:[99,108],instanci:102,instanti:[99,108],instead:[16,41,72,99,108],integ:[34,41,99,104,105,108],intend:[93,108],intens:[72,108],interact:[41,99,108],intercept:[93,108],interfac:[7,14,35,99,100,101,108],interleav:[99,108],interpret:[41,99,108],interrupt:[99,108],introspect:[10,108],intuit:[99,108],inv:[93,108],inv_map:[98,108],invalid:[41,95,108],invers:[34,35,93,98,108],inverse_affine_convert:[93,108],invert:[98,108],invertible_map:[98,108],invoc:[99,108],invok:[99,108],irrelev:[7,108],is_automatic_format_param:[34,108],is_automatic_format_str:[34,108],is_hybrid_format_param:[34,108],is_hybrid_format_str:[34,108],is_int:[99,108],is_iter:[99,108],is_manual_format_param:[34,108],is_manual_format_str:[34,108],is_non_string_sequ:102,isdir:[79,108],isfil:[79,108],isinst:[99,108],iskeyword:95,islicestor:[104,108],isn:[0,99,108],iso:[41,108],issu:[41,108],issuperset:[79,108],item2kv:6,item:[4,6,18,36,41,79,97,99,100,108],item_to_kei:[97,108],item_to_kv:[97,108],iter:[4,34,41,53,89,98,99,102,104,108],its:[15,99,108],itself:[16,72,98,99,108],jame:[41,108],jan:[35,41,108],jen:[41,108],job:[72,108],john:[41,108],join:[15,36,41,53,79,97,99,108],jone:[41,108],json:[5,17,24,36,72,79,95,108],julia:[41,108],jump:[35,108],junk:[72,108],just:[0,6,16,35,41,72,97,98,99,108],kapouil:102,keep:[16,41,99,108],kei:[0,5,6,7,15,16,18,31,35,36,53,72,73,79,95,97,98,99,100,104,108],kept:[19,33,108],key_collect:[98,108],key_concat:[100,108],key_mapp:[0,2,79,107],key_of_id:[98,108],key_to_obj:[15,108],key_within_that_zip_fil:0,keyerror:[99,108],keyvalidationerror:[35,108],keyword:[95,99,108],kilcher:[41,108],kind:[35,99,108],king:[41,108],knight:[41,108],know:[7,36,72,89,99,108],knowledg:16,kv_walk:[2,107],kvhead:[4,108],kvreader:[72,108],kwarg:[15,35,66,79,99,100,108],lai:[5,108],lambda:[15,36,72,79,89,97,99,100,108],languag:[0,7,41,102,108],larg:[105,108],last:[16,35,72,98,99,108],last_key_insert:[2,107],later:[5,36,108],latest:[99,108],layer:[4,7,8,10,23,25,27,36,71,72,95,108],lazi:[96,108],lead:[41,108],leaf:[100,108],leafmappifi:[100,108],least:[34,108],leav:[99,108],left:[41,99,108],len:[0,72,79,98,99,108],length:[0,53,89,108],less:[5,19,41,99,108],let:[0,36,99,108],letter:[41,97,108],level:[5,99,108],levi:[41,108],liabil:[99,108],liabl:[99,108],lib:[70,71,108],librari:[43,72,99,108],licens:[41,99,108],lighter:[99,108],lightweight:[99,108],like:[0,5,17,29,35,36,41,42,72,89,95,99,100,102,108],limit:[41,99,108],line:[17,108],link:[99,108],linux:[41,108],lisp:[99,108],list:[4,7,15,19,34,35,36,72,79,89,97,98,99,100,104,105,108],list_of_dsv:[35,108],listdir:[79,108],liter:[99,108],literal_str:[99,108],literal_text:[34,108],load:[5,36,66,79,108],loader:[5,108],local:[14,36,41,53,72,79,84,97,99,108],local_fil:[2,36,107],local_files_test:[2,107],local_stor:[2,107],localbinarystor:[79,108],localfilerwd:[53,108],localfilestreamgett:[53,108],localhost:[106,108],localjsonstor:[79,108],localpicklestor:[79,108],localstor:[79,108],localtextstor:[79,108],log:[41,108],logger:[41,108],logic:[5,99,108],longer:[35,108],look:[5,36,41,98,99,108],lookup:[99,108],loop:[6,93,108],loss:[99,108],lot:[41,93,108],love:[41,72,99,108],lower:[41,97,108],luciano:95,mac:[72,108],made:[89,108],magna:[36,108],mahmoud:[99,108],mai:[41,99,108],mail:[41,108],main:[0,5,6,99,108],maintain:[99,108],major:[99,108],make:[5,7,14,16,35,36,41,72,79,89,94,97,98,99,104,108],make_sentinel:[99,108],makemissingdirsstoremixin:[79,108],mani:[4,41,99,108],manipul:42,manner:[79,108],manual:[34,35,41,89,108],manual_field_format_str:[34,108],map:[14,15,31,35,36,41,95,98,99,100,104,108],mapper:[35,108],mappifi:[2,107],mark:[34,41,99,108],marker:[99,108],marshal:[66,108],martijn:[41,108],master:[24,108],match:[2,99,102,107],matcher:[41,108],materi:[99,108],matlab:[2,107],max_level:[53,72,79,108],maximum:[41,108],maxlen:[105,108],mean:[7,16,29,108],mean_lines_per_funct:[17,108],meaning:[99,108],meant:[7,38,108],meet:[41,108],merchant:[99,108],mess:[41,108],messag:[99,108],met:[97,99,108],method:[6,7,15,41,93,99,108],mg_selector:[2,107],microsecond:[41,105,108],middl:[99,108],might:[7,16,41,72,99,108],min:[18,108],mini:[41,108],minimum:[41,108],mirror:[41,108],misc:[2,107],misc_obj:[36,108],misc_objs_get:[36,108],miscgett:[36,108],miscgetterandsett:[36,108],miscread:[36,108],miscreadermixin:[36,108],miscstor:[36,108],miscstoremixin:[36,108],mismatch:[41,108],miss:[79,99,108],mix:[41,99,108],mixin:[2,36,53,79,107],mk_cach:[97,108],mk_flatzips_stor:[72,108],mk_group_aggreg:[97,108],mk_group_aggregator_with_key_func:[97,108],mk_kv_from_keygen:[97,108],mk_marshal_rw_func:[66,108],mk_obj_of_str:[35,108],mk_pickle_rw_func:[66,108],mk_store:[72,108],mk_str_from_obj:[35,108],mk_str_of_obj:[35,108],mkdir:[79,108],mkdtemp:[53,108],mmm:[41,108],mode:[53,79,99,108],modif:[99,108],modifi:[41,102,108],modul:[0,2,7,12,19,29,32,33,40,41,42,70,73,99,108],modules_info_df:[17,108],modules_info_df_stat:[17,108],modulo:[97,108],mon:[41,108],mongo:[101,102],mongodb:102,mongoqueri:[2,107],month:[41,108],more:[5,7,19,34,41,66,72,79,89,93,99,104,108],most:[0,35,41,72,98,99,108],mostli:[99,108],move:[17,99,108],mro:[79,108],msg:[99,108],much:[99,108],multipl:[72,99,108],multipli:[99,108],must:[41,98,99,108],mutablemap:[7,108],my_filt_func:[72,108],n_format_params_in_str_format:[34,108],nada:[99,108],nage:[41,108],nake:29,name:[0,2,5,16,24,34,41,72,79,89,93,95,96,99,107],name_fields_in_format_str:[34,108],names_of_liter:[100,108],nan:[41,108],natur:[72,99,108],navig:95,ncolor:[41,108],ndigit:[99,108],necessari:[41,97,108],necku:[41,108],need:[7,34,36,41,72,79,93,97,98,99,105,108],neglig:[99,108],nest:[15,36,99,100,108],never:[99,108],new_s3:[2,107],next:[99,108],nltk:29,node:[15,108],node_typ:[100,108],noiniti:[97,108],non:[34,41,79,97,99,108],none:[5,14,16,24,34,36,41,66,72,79,93,95,96,98,99,105,106,108],normal:[36,99,108],not_ther:[79,108],notat:95,note:[16,17,34,35,36,41,72,89,93,99,104,108],noth:[36,41,108],notic:[99,108],nov:[41,108],now:[41,43,72,79,98,99,108],num_of_class:[17,108],num_of_funct:[17,108],number2:[41,108],number:[0,7,34,36,41,89,97,108],numer:[41,108],obj:[79,99,108],obj_nam:[5,108],obj_source_test_2:[79,108],object:[0,2,4,5,7,11,23,25,27,35,36,42,53,66,71,72,89,91,95,97,98,99,100,102,104,107],objread:[98,108],occur:[35,41,99,108],occurr:[41,99,108],ocor:[93,108],octal:[41,108],odd:[97,108],odir:[72,108],off:[35,41,108],offer:[7,108],offset:[41,93,108],often:[41,72,99,108],ogl:[24,108],omit:[16,41,99,108],onc:[5,41,72,99,108],one:[0,5,7,34,35,41,72,79,98,99,105,108],one_of:[99,108],onli:[14,16,19,32,34,35,36,53,72,79,95,99,100,108],only_if_new_kei:16,ons:[19,108],op_nam:[99,108],open:[36,53,79,98,108],open_kw:[72,108],open_kwarg:[53,79,108],oper:[40,41,79,93,99,108],opposit:[41,108],optim:[89,108],option:[5,41,72,93,99,108],ord:[36,97,108],order:[0,19,34,41,99,108],org:[29,41,72,108],orient:[99,108],origin:[99,108],other:[5,17,29,41,72,93,98,99,108],otherwis:[41,99,108],our:[0,36,41,99,108],out:[5,35,41,72,93,99,108],outgo:[36,108],outgoing_val_trans_for_kei:[36,108],output:[41,89,93,97,99,104,108],over:[35,41,99,108],overflow:[41,108],overkil:16,overlap:[99,108],overrid:[36,41,108],overwrit:[41,108],overwritenotallow:[72,108],overwritten:[105,108],own:[17,41,79,108],owner:[99,108],packag:[17,41,63,74,99,108],pad:[41,108],page:2,pair:[0,7,66,98,99,108],panda:[21,108],param:[34,35,72,89,93,106,108],paramet:[5,15,16,34,35,41,89,97,99,108],parametr:[5,35,40,66,108],parent:[41,99,108],parenthesi:[41,108],pars:[34,35,41,106,108],parse_format:[2,107],parse_numb:[41,108],parse_number2:[41,108],parse_str_format:[34,108],parse_uri:[106,108],parse_yesno:[41,108],parsed_str_format:[34,108],parser:[41,108],part:[41,99,108],part_idx:[99,108],partial:[99,108],particular:[0,99,108],partli:[99,108],pass:[16,41,99,108],password:[106,108],path:[0,2,14,15,36,40,53,72,79,98,99,107],path_format:[53,79,108],path_format_store_test:[79,108],path_part:[99,108],pathaccesserror:[99,108],pathformatpersist:[36,53,108],pathformatstor:[79,108],pathformatstorewithprefix:[79,108],pattern:[41,96,108],pattern_for_field:[72,108],pdb:[99,108],per:[41,108],percentag:[41,108],perform:[93,99,108],perisist:[79,108],permiss:[99,108],permit:[99,108],persist:[2,7,36,79,107],perspect:[72,108],pickl:[2,36,79,107],pickle_encod:[66,79,108],pickle_error:[66,79,108],pickleabl:[99,108],picklestor:[79,108],pieter:[41,108],pip:[17,29,108],pipe:[41,108],pipelin:[5,108],pjoin:[36,108],pkl:[36,108],pkv_to_pv:[15,108],place:[36,41,79,99,108],placehold:[99,108],placement:[34,108],plc:[72,108],point:[0,41,53,79,99,108],popul:[105,108],port:[106,108],portal:[4,108],pos:[41,108],posit:[34,41,99,108],possibl:[0,41,99,105,108],post_mortem:[99,108],postget:[36,108],potenti:[2,107],power:[19,99,108],pprint:[99,108],practic:[0,99,108],precis:[41,108],predic:[99,108],prefix:[41,72,108],prefixeddirpathsrecurs:[53,108],prefixedfilepath:[53,108],prefixedfilepathsrecurs:[53,108],prepar:[79,108],presenc:[41,108],present:[5,41,53,99,108],preset:[36,108],pretend:[36,105,108],pretti:[99,108],previous:[24,108],princeton:29,print:[0,24,34,36,41,53,98,99,100,108],prior:[99,108],problem:[0,5,41,99,108],problemat:[36,108],process:[99,108],procur:[99,108],produc:[34,89,99,108],product:[99,108],profit:[99,108],programmat:[99,108],project:[24,99,108],project_nam:[24,108],promis:[99,108],promot:[99,108],prompt:[99,108],proper:[89,108],properti:[36,96,108],protocol:[66,79,108],provid:[0,19,41,53,72,99,102,106,108],pull:[41,108],pure:[19,108],purpos:[99,108],put:[29,36,108],pwd:[72,108],py2stor:[0,107],py2store_config:[5,108],py2store_configs_json_filepath:[5,108],py2store_default:[5,108],py2store_defaults_json_filepath:[5,108],pydrivedol:[52,108],python:[5,7,29,41,72,95,99,102,108],python_code_stat:[2,107],quasi:[34,99,108],queri:[101,102],queryerror:102,quest:[41,108],question:[41,99,108],queue:[99,108],quick:[36,85],quick_test:[2,107],quickbinarystor:[79,108],quickjsonstor:[79,108],quicklocalstoremixin:[79,108],quickpicklestor:[79,108],quickstor:[79,108],quicktextstor:[79,108],quit:[99,108],quot:[99,108],r1chardj0n3:[41,108],rais:[41,98,99,108],raise_exc:[99,108],ramalho:95,random:[89,108],random_dict_gen:[89,108],random_formatted_str_gen:[89,108],random_str:[89,108],random_tuple_gen:[89,108],random_word:[89,108],random_word_gen:[89,108],randomli:[89,108],rang:[41,89,99,104,108],rant:[72,108],rare:[99,108],rate:[7,108],rather:[99,108],raw:[36,108],read:[7,14,35,36,53,66,72,95,98,99,105,108],read_dict:[98,108],read_fil:[98,108],readabl:[99,108],reader:[36,53,66,72,98,104,108],readlin:[72,108],readthedoc:[99,108],realli:[72,108],reason:[93,108],receiv:[79,108],recent:[98,99,108],record:[99,108],recreat:[79,108],recurs:[53,99,108],red:[41,108],redis_w_redi:[2,107],redisdol:[56,108],redistribut:[99,108],reduc:[97,108],redund:[41,108],refactor:[41,108],refer:[99,108],referenc:[5,99,108],refin:[99,108],regard:[41,108],regex:[41,108],regex_group_count:[41,108],regist:[99,108],register_default_typ:[99,108],register_op:[99,108],registr:[99,108],regular:[41,108],regulartimeseriescach:[105,108],rel:[79,105,108],relat:[99,108],relative_path:[79,108],relative_zip_filepath:0,relativedirpathformatkei:[79,108],relativepathformatstore2:[79,108],releas:[41,99,108],relev:[5,108],reli:[99,108],relpath:[72,108],rememb:[16,99,108],remember_last_key_written_to:16,remind:[5,108],remot:[7,108],remov:[5,41,72,79,108],rendit:[99,108],repeat:[41,89,99,108],repetit:[34,108],replac:[99,108],repr:[99,108],repres:[7,41,99,108],represent:[7,108],reproduc:[99,108],request:[24,41,108],requir:[19,108],reserv:95,resolv:[34,72,108],resourc:[5,41,108],respect:[5,99,108],respons:[99,108],restrict:[41,89,108],restructur:[99,108],result:[2,99,107],retain:[99,108],retriev:[36,108],reus:[5,108],revers:[99,106,108],reverse_thi:[36,108],review:[99,108],revolv:[99,108],rfc2822:[41,108],rfc:[41,108],richard:[41,108],right:[5,41,98,99,108],rin:[35,108],rmdir:[79,108],robustif:[41,108],root:[34,41,53,79,89,98,108],rootdir:[53,72,79,108],rootdir_2:[79,108],roughli:[99,108],round:[99,108],round_2:[99,108],roundtrip:[99,108],rout:95,row:[17,41,108],rufu:[41,108],run:[72,99,108],runtim:[99,108],s3_store:[2,107],s3_w_boto3:[2,107],s3dol:[55,57,81,108],s_configpars:[2,107],s_zipfil:[0,2,36,107],safe:16,sai:[7,16,34,41,79,104,108],said:[19,108],same:[5,7,41,72,79,89,99,108],same_name_as_class:16,sampl:[7,108],sat:[35,108],save:[5,108],scale:[93,108],schema:[5,35,108],scheme:[106,108],scope:[99,108],scrap:[2,107],search:[2,41,72,108],sebastian:[41,108],second:[0,41,99,108],section:[99,108],see:[0,6,16,29,34,35,36,41,79,93,99,108],seed:[89,99,108],seek:[41,108],seem:[99,108],seen:[99,108],segment:[105,108],selector:[101,102],self:[36,96,99,108],semant:[99,108],sens:[41,108],sensit:[41,108],sentinel:[99,108],sep:[35,41,79,108],separ:[35,41,43,53,97,108],sequenc:[97,99,102,108],seri:[105,108],serial:[2,5,7,36,79,107],serialized_d:[66,108],serv:[72,99,108],server:[7,108],servic:[99,108],session_id:[72,108],set:[24,29,34,36,41,53,79,98,99,108],set_obj:[36,108],set_of_fields_us:[34,108],set_of_indices_us:[34,108],set_project:[24,108],set_trac:[99,108],setitem:[79,108],setup:[19,41,99,108],sever:[0,99,108],shall:[99,108],share:[5,29,108],shortest:[41,108],shorthand:[99,108],should:[0,5,16,35,36,41,72,79,97,98,99,104,108],shouldn:[7,99,108],shouti:[41,108],show:[16,19,99,108],shrubberi:[41,108],sidenot:[99,108],sign:[41,108],signatur:[2,107],signel:[41,108],signific:[41,108],similar:[99,108],simpl:[22,41,66,99,100],simpler:[0,41,108],simpli:[99,108],simplic:[99,108],sinc:[36,72,79,93,108],singl:[41,93,108],singleton:[35,99,108],size:[35,41,89,108],skip:[29,99,108],skip_exc:[99,108],slash:[72,108],slib:[0,2,36,107],slice:[41,99,104,108],sliceabl:[2,107],sliceable_:[104,108],slightli:[41,108],slope:[93,108],small:[99,108],softwar:[99,108],some:[5,7,16,41,72,79,97,98,99,108],some_folder_in_zip:0,some_zip_fil:[0,72,108],someth:[72,99,108],sometim:[0,72,89,99,108],sort:[41,72,79,99,108],sourc:[2,4,5,14,15,16,18,21,34,35,36,41,53,66,72,79,89,93,95,96,97,98,99,100,102,104,105,106,107],source_type_cast:[93,108],space:[41,108],spam:[41,108],span:[41,108],spec:[34,41,99,108],specfunc:[99,108],special:[0,19,99,108],specif:[2,5,7,34,35,89,99,107],specifi:[5,7,41,72,93,98,99,102,108],sql:[99,108],sql_w_sqlalchemi:[2,107],sqldol:[59,82,108],src:[15,108],src_to_kei:[15,108],srcreader:[15,108],sss:16,st0:[35,108],stack:[99,108],stai:[99,108],standard:[70,71,108],star:[99,108],start:[36,41,97,99,108],startswith:[72,108],stat:[17,108],state:[99,108],statement:16,stats_of:[17,108],statu:[72,108],step:[99,108],still:[16,99,108],sting:40,stop:[99,108],storag:[7,18,19,53,108],store:[0,2,4,5,6,7,8,15,16,18,24,36,39,72,94,97,98,99,104,107],store_cl:6,str:[5,35,36,41,53,89,99,108],str_format:[35,108],str_from_obj:[35,108],str_of_tupl:[35,108],str_util:[2,107],strai:[99,108],straight:[41,108],straightforward:102,stream:[53,72,108],stress:[41,108],strict:[66,79,99,108],string:[5,34,35,41,79,89,97,99,102,106,108],strip:[41,108],structur:[5,15,35,99,108],stuff:[0,29,72,98,99,108],stunt:[99,108],style:[93,99,108],sub:[6,99,108],subclass:[6,36,72,108],subdir:[41,108],subdirectori:[53,79,108],subfold:[0,72,108],subpath:[72,108],subsequ:[99,108],subspec:[99,108],substitut:[41,99,108],subsystem:[99,108],subtyp:[99,108],success:[41,99,108],successor:[99,108],suffic:[41,108],suffix:[5,108],suggest:[35,108],suitabl:[99,108],sum:[97,99,108],sun:[41,108],suppli:[41,108],support:[41,99,108],sure:[41,108],surpass:[99,108],synonym:29,synset:29,syntax:[2,99,107],system:[5,18,19,30,41,72,108],tail:[34,108],take:[36,72,79,97,99,108],take_everyth:[72,108],taken:[100,108],talk:[24,108],tape:[99,108],target:[99,100,108],target_typ:[99,108],target_type_cas:[93,108],target_type_cast:[93,108],targetregistri:[99,108],temp:[14,79,108],tempfil:[53,79,108],templat:[35,79,99,108],temporari:[79,108],temporarili:[89,108],term:[99,108],termin:29,test:[2,16,41,99,107],test_phas:[72,108],test_phase_numb:[72,108],testact:[72,108],text:[35,36,41,79,99,108],textual:[41,108],than:[19,41,93,99,108],thank:[41,108],the_oth:[79,108],thei:[0,5,7,34,39,41,72,98,99,105,108],them:[16,72,98,99,108],themselv:[7,108],theori:[99,108],thi:[0,5,7,16,29,32,34,36,40,41,53,72,73,79,89,93,96,98,99,105,108],thiel:[41,108],thing:[17,29,41,72,79,96,97,99,108],think:[99,108],those:[7,41,99,108],though:[41,72,79,93,99,108],thousand:[41,108],thread:16,three:[0,41,99,108],through:[0,15,72,93,99,108],time:[41,98,99,105,108],time_r:[105,108],times_3:[99,108],timeseries_cach:[2,107],timestamp:[18,99,105,108],timestamp_on_cache_and_concatenate_all_valu:[18,108],timezon:[41,108],timo:[41,108],tmp:[14,36,53,108],todo:[99,108],toe:[99,108],togeth:[89,97,99,108],too:[35,41,72,108],took:[99,108],tool:[0,5,6,8,9,19,35,90,105,108],toomanyfield:[41,108],top:[99,108],tort:[99,108],touch:[99,108],trace:[99,108],traceback:[98,99,108],track:[16,99,108],trail:[41,108],tran:[0,2,36,72,107],transform:[34,35,36,90,93,97,99,102,108],transpar:[99,108],tri:[99,108],tricki:[99,108],trivial:[99,108],truthi:[99,108],ttype:[99,108],tupl:[2,5,7,34,41,89,98,99,107],tuple_keypath_and_v:[15,108],tuple_length:[89,108],turn:[99,108],tutori:[99,108],tweak:[41,108],twhalen:[72,108],two:[5,34,41,72,79,93,97,98,99,108],txt:[36,53,79,108],type:[0,2,7,89,93,98,99,105,107],type_map:[99,108],typeerror:[99,108],typic:[99,108],umpyr:[17,108],unambigu:[99,108],uncaught:[99,108],uncommon:[99,108],under:[36,53,79,108],underli:[99,108],underscor:[41,108],undesir:[99,108],unexpect:[99,108],unic:0,union:[72,108],uniqu:0,unix:[72,108],unlik:[35,99,108],unnam:[34,41,108],unregisteredtarget:[99,108],unset:[99,108],unsupport:[99,108],until:[5,99,108],unwrap:[99,108],updat:[15,16,99,108],update_keys_cach:[15,108],upon:[99,108],upper:[41,108],uri:[106,108],uri_util:[2,107],url:14,urllib:[17,108],usabl:[96,108],usag:[41,99,108],use:[0,7,16,34,36,41,72,99,101,108],used:[15,19,34,35,36,41,72,93,97,99,102,108],useful:[34,72,99,108],user:[0,5,19,41,72,93,105,108],user_config:[5,108],user_default:[5,108],usernam:[106,108],uses:[0,12,18,34,41,98,99,108],using:[0,14,19,24,35,36,41,53,66,72,79,95,99,105,108],usual:[35,72,99,108],utc:[41,105,108],util:[2,5,11,34,107],uuid:[99,108],v18:[99,108],val:[6,36,79,93,99,108],val_is_map:[15,108],valid:[5,34,35,40,41,72,95,99,108],valu:[0,5,7,15,16,35,36,41,53,72,73,79,97,98,99,108],valueerror:[41,98,108],vandenberg:[41,108],var_nam:[99,108],variabl:[5,99,108],varieti:[41,108],variou:[12,18,21,74,108],vast:[99,108],veri:[99,105,108],verifi:[36,95,108],version:[34,41,99,108],via:[99,108],view:[72,73,108],visibl:[99,108],visser:[41,108],w_aiofil:[2,107],wai:[29,36,99,108],walk:[15,99,108],walk_filt:[15,108],want:[0,5,6,7,15,16,34,35,36,39,72,89,95,97,99,108],warn:[36,108],warranti:[99,108],wav:[7,108],waveform:[7,36,105,108],weapon:[41,108],weird:[72,108],well:[0,34,35,36,72,99,108],were:[97,108],weren:[99,108],what:[0,7,15,16,29,35,36,89,98,99,108],whatev:[41,79,108],when:[0,35,41,72,79,96,97,99,104,108],where:[0,5,7,15,41,53,99,105,108],wherea:[99,108],wherev:[99,108],whether:[15,99,108],which:[5,41,72,99,108],whirl:[99,108],whitespac:[41,108],who:[41,108],wholesom:[99,108],whose:[0,53,72,79,89,99,108],why:[35,108],width:[41,108],wish:[35,41,108],with_pattern:[41,108],within:[7,41,99,108],without:[41,99,108],won:[72,99,108],word:[22,29,89,97,108],word_size_rang:[89,108],wordnet:[2,107],work:[0,7,35,53,72,96,99,106,108],world:[34,35,36,41,53,104,108],worri:[93,99,108],would:[0,16,34,41,99,108],wrap:[19,36,41,72,90,98,99,100,104,108],wrap_kv:[0,36,108],wrapper:[29,99,108],write:[7,18,35,36,53,66,72,79,97,99,108],write_cach:[2,107],write_to_kei:[79,108],writer:[66,108],written:[16,36,99,108],wrote:16,www:29,xlsx:0,yeah:[36,108],year:[36,108],yep:[36,108],yes:[41,108],yesno_map:[41,108],yet:[34,41,108],yield:[15,34,89,108],yield_func:[15,108],you:[0,5,6,7,15,16,29,34,35,36,39,41,72,79,89,98,99,108],your:[0,4,5,17,29,41,72,99,108],yourself:[72,108],zero:[99,108],zerodivisionerror:[99,108],zip:[36,72,108],zip_fil:[72,108],zip_filepath:[72,108],zip_pair_path_preproc:[72,108],zip_path_pair:[72,108],zip_read:[72,108],zip_reader_kwarg:[72,108],zipdir:0,zipfil:[72,108],zipfileread:[72,108],zipfilesread:[72,108],zipfilesreaderandbyteswrit:[72,108],zipfilestreamsread:[72,108],zipinfo:[72,108],zipread:[0,72,108],zipstor:[72,108]},titles:["A reader of multiple zip files","<no title>","Welcome to py2store\u2019s documentation!","Mock Objects","py2store","py2store.access","py2store.appendable","py2store.base","py2store.caching","py2store.core","py2store.dig","py2store.errors","py2store.examples","py2store.examples.code_navig","py2store.examples.dropbox_w_urllib","py2store.examples.kv_walking","py2store.examples.last_key_inserted","py2store.examples.python_code_stats","py2store.examples.write_caches","py2store.ext","py2store.ext.audio","py2store.ext.dataframes","py2store.ext.docx","py2store.ext.github","py2store.ext.gitlab","py2store.ext.hdf","py2store.ext.kaggle","py2store.ext.matlab","py2store.ext.module_imports","py2store.ext.wordnet","py2store.filesys","py2store.key_mappers","py2store.key_mappers.naming","py2store.key_mappers.paths","py2store.key_mappers.str_utils","py2store.key_mappers.tuples","py2store.misc","py2store.mixins","py2store.my","py2store.my.grabbers","py2store.naming","py2store.parse_format","py2store.paths","py2store.persisters","py2store.persisters._postgres_w_psycopg2_in_progress","py2store.persisters.arangodb_w_pyarango","py2store.persisters.couchdb_w_couchdb","py2store.persisters.dropbox_w_dropbox","py2store.persisters.dropbox_w_requests","py2store.persisters.dropbox_w_urllib","py2store.persisters.dynamodb_w_boto3","py2store.persisters.ftp_persister","py2store.persisters.googledrive_w_pydrive","py2store.persisters.local_files","py2store.persisters.mongo_w_pymongo","py2store.persisters.new_s3","py2store.persisters.redis_w_redis","py2store.persisters.s3_w_boto3","py2store.persisters.sql_w_odbc","py2store.persisters.sql_w_sqlalchemy","py2store.persisters.ssh_persister","py2store.persisters.w_aiofile","py2store.scrap.new_gen_local","py2store.serializers","py2store.serializers.audio","py2store.serializers.jsonization","py2store.serializers.pickled","py2store.serializers.regular_panel_data","py2store.serializers.sequential","py2store.signatures","py2store.slib","py2store.slib.s_configparser","py2store.slib.s_zipfile","py2store.sources","py2store.stores","py2store.stores.arangodb_store","py2store.stores.couchdb_store","py2store.stores.delegation_stores","py2store.stores.dropbox_store","py2store.stores.local_store","py2store.stores.mongo_store","py2store.stores.s3_store","py2store.stores.sql_w_sqlalchemy","py2store.test","py2store.test.local_files_test","py2store.test.quick_test","py2store.test.scrap","py2store.test.simple_test","py2store.test.trans_test","py2store.test.util","py2store.trans","py2store.util","py2store.utils","py2store.utils.affine_conversion","py2store.utils.appendable","py2store.utils.attr_dict","py2store.utils.cache_descriptors","py2store.utils.cumul_aggreg_write","py2store.utils.explicit","py2store.utils.glom","py2store.utils.mappify","py2store.utils.mg_selectors","py2store.utils.mongoquery","py2store.utils.signatures","py2store.utils.sliceable","py2store.utils.timeseries_caching","py2store.utils.uri_utils","<no title>","py2store.filesys"],titleterms:{__init__:108,_cassandra_in_progress:108,_couchdb_in_progress:108,_google_drive_in_progress:108,_postgres_w_psycopg2_in_progress:[44,108],access:[5,108],affine_convers:[93,108],append:[6,94,108],arangodb_stor:[75,108],arangodb_w_pyarango:[45,108],attr_dict:[95,108],audio:[20,64,108],base:[7,108],cach:[8,108],cache_descriptor:[96,108],code_navig:13,convers:[41,108],core:[9,108],couchdb_stor:[76,108],couchdb_w_couchdb:[46,108],cumul_aggreg_writ:[97,108],custom:[41,108],datafram:[21,108],delegation_stor:[77,108],dig:[10,108],document:2,docx:[22,108],dropbox_stor:[78,108],dropbox_w_dropbox:[47,108],dropbox_w_request:[48,108],dropbox_w_urllib:[14,49,108],dynamodb_w_boto3:[50,108],error:[11,108],exampl:[12,13,14,15,16,17,18,108],explicit:[98,108],ext:[19,20,21,22,23,24,25,26,27,28,29,108],file:0,filesi:[30,108],flatzipfilesread:0,format:[41,108],ftp_persist:[51,108],github:[23,108],gitlab:[24,108],glom:[99,108],googledrive_w_pydr:[52,108],gotcha:[41,108],grabber:[39,108],hdf:[25,108],indic:2,jsoniz:[65,108],kaggl:[26,108],key_mapp:[31,32,33,34,35,108],kv_walk:[15,108],last_key_insert:16,local_fil:[53,108],local_files_test:84,local_stor:[79,108],mappifi:[100,108],match:[41,108],matlab:[27,108],mg_selector:[101,108],misc:[36,108],mixin:[37,108],mk_flatzips_stor:0,mock:3,module_import:[28,108],mongo_stor:[80,108],mongo_w_pymongo:[54,108],mongoqueri:[102,108],multipl:0,name:[32,40,108],new_gen_loc:[62,108],new_s3:[55,108],object:[3,41,108],parse_format:[41,108],path:[33,42,108],persist:[43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,108],pickl:[66,108],potenti:[41,108],py2stor:[2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,108],python_code_stat:[17,108],quick:108,quick_test:85,reader:0,redis_w_redi:[56,108],regular_panel_data:[67,108],result:[41,108],s3_store:[81,108],s3_w_boto3:[57,108],s_configpars:[71,108],s_zipfil:[72,108],scrap:[62,86,108],selector:108,sequenti:[68,108],serial:[63,64,65,66,67,68,108],signatur:[69,103,108],simpl:108,simple_test:87,slib:[70,71,72,108],sliceabl:[104,108],sourc:[73,108],specif:[41,108],sql_w_odbc:[58,108],sql_w_sqlalchemi:[59,82,108],ssh_persist:[60,108],store:[74,75,76,77,78,79,80,81,82,108],str_util:[34,108],syntax:[41,108],tabl:2,test:[83,84,85,86,87,88,89,108],timeseries_cach:[105,108],tran:[90,108],trans_test:88,tupl:[35,108],type:[41,108],uri_util:[106,108],util:[89,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,108],w_aiofil:[61,108],welcom:2,wordnet:29,write_cach:[18,108],zip:0,zipfilesread:0}}) \ No newline at end of file diff --git a/docs/table_of_contents.html b/docs/table_of_contents.html deleted file mode 100644 index 65d22aa..0000000 --- a/docs/table_of_contents.html +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - - - - <no title> — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Contents:

- -
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docs/test.html b/docs/test.html deleted file mode 100644 index da277a3..0000000 --- a/docs/test.html +++ /dev/null @@ -1,3877 +0,0 @@ - - - - - - - - - py2store.filesys — py2store 0.1.2 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

py2store.filesys

-

Forwards to dol.filesys:

-

File system access

-
-
-

py2store.misc

-

Functions to read from and write to misc sources

-
-
-class py2store.misc.MiscGetter(store=<py2store.persisters.local_files.PathFormatPersister object>, incoming_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function <lambda>>, '.gz': <function decompress>, '.gzip': <function decompress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>, '.zip': <class 'py2store.slib.s_zipfile.FilesOfZip'>}, dflt_incoming_val_trans=<function identity_method>, func_key=<function MiscGetter.<lambda>>)[source]
-

An object to write (and only write) to a store (default local files) with automatic deserialization -according to a property of the key (default: file extension).

-
>>> from py2store.misc import get_obj, misc_objs_get
->>> import os
->>> import json
->>>
->>> pjoin = lambda *p: os.path.join(os.path.expanduser('~'), *p)
->>> path = pjoin('tmp.json')
->>> d = {'a': {'b': {'c': [1, 2, 3]}}}
->>> json.dump(d, open(path, 'w'))  # putting a json file there, the normal way, so we can use it later
->>>
->>> k = path
->>> t = get_obj(k)  # if you'd like to use a function
->>> assert t == d
->>> tt = misc_objs_get[k]  # if you'd like to use an object (note: can get, but nothing else (no list, set, del, etc))
->>> assert tt == d
->>> t
-{'a': {'b': {'c': [1, 2, 3]}}}
-
-
-
- -
-
-class py2store.misc.MiscGetterAndSetter(store=<py2store.persisters.local_files.PathFormatPersister object>, incoming_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function <lambda>>, '.gz': <function decompress>, '.gzip': <function decompress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>, '.zip': <class 'py2store.slib.s_zipfile.FilesOfZip'>}, outgoing_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function csv_fileobj>, '.gz': <function compress>, '.gzip': <function compress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>}, dflt_incoming_val_trans=<function identity_method>, func_key=<function MiscGetterAndSetter.<lambda>>)[source]
-

An object to read and write (and nothing else) to a store (default local) with automatic (de)serialization -according to a property of the key (default: file extension).

-
>>> from py2store.misc import set_obj, misc_objs  # the function and the object
->>> import json
->>> import os
->>>
->>> pjoin = lambda *p: os.path.join(os.path.expanduser('~'), *p)
->>>
->>> d = {'a': {'b': {'c': [1, 2, 3]}}}
->>> misc_objs[pjoin('tmp.json')] = d
->>> filepath = os.path.expanduser('~/tmp.json')
->>> assert misc_objs[filepath] == d  # yep, it's there, and can be retrieved
->>> assert json.load(open(filepath)) == d  # in case you don't believe it's an actual json file
->>>
->>> # using pickle
->>> misc_objs[pjoin('tmp.pkl')] = d
->>> assert misc_objs[pjoin('tmp.pkl')] == d
->>>
->>> # using txt
->>> misc_objs[pjoin('tmp.txt')] = 'hello world!'
->>> assert misc_objs[pjoin('tmp.txt')] == 'hello world!'
->>>
->>> # using csv
->>> misc_objs[pjoin('tmp.csv')] = [[1,2,3], ['a','b','c']]
->>> assert misc_objs[pjoin('tmp.csv')] == [['1','2','3'], ['a','b','c']]  # yeah, well, not numbers, but you deal with it
->>>
->>> # using bin
-... misc_objs[pjoin('tmp.bin')] = b'let us pretend these are bytes of an audio waveform'
->>> assert misc_objs[pjoin('tmp.bin')] == b'let us pretend these are bytes of an audio waveform'
-
-
-
- -
-
-class py2store.misc.MiscReaderMixin(incoming_val_trans_for_key=None, dflt_incoming_val_trans=None, func_key=None)[source]
-

Mixin to transform incoming vals according to the key their under. -Warning: If used as a subclass, this mixin should (in general) be placed before the store

-
>>> # make a reader that will wrap a dict
->>> class MiscReader(MiscReaderMixin, dict):
-...     def __init__(self, d,
-...                         incoming_val_trans_for_key=None,
-...                         dflt_incoming_val_trans=None,
-...                         func_key=None):
-...         dict.__init__(self, d)
-...         MiscReaderMixin.__init__(self, incoming_val_trans_for_key, dflt_incoming_val_trans, func_key)
-...
->>>
->>> incoming_val_trans_for_key = dict(
-...     MiscReaderMixin._incoming_val_trans_for_key,  # take the existing defaults...
-...     **{'.bin': lambda v: [ord(x) for x in v.decode()], # ... override how to handle the .bin extension
-...      '.reverse_this': lambda v: v[::-1]  # add a new extension (and how to handle it)
-...     })
->>>
->>> import pickle
->>> d = {
-...     'a.bin': b'abc123',
-...     'a.reverse_this': b'abc123',
-...     'a.csv': b'event,year\n Magna Carta,1215\n Guido,1956',
-...     'a.txt': b'this is not a text',
-...     'a.pkl': pickle.dumps(['text', [str, map], {'a list': [1, 2, 3]}]),
-...     'a.json': '{"str": "field", "int": 42, "float": 3.14, "array": [1, 2], "nested": {"a": 1, "b": 2}}',
-... }
->>>
->>> s = MiscReader(d=d, incoming_val_trans_for_key=incoming_val_trans_for_key)
->>> list(s)
-['a.bin', 'a.reverse_this', 'a.csv', 'a.txt', 'a.pkl', 'a.json']
->>> s['a.bin']
-[97, 98, 99, 49, 50, 51]
->>> s['a.reverse_this']
-b'321cba'
->>> s['a.csv']
-[['event', 'year'], [' Magna Carta', '1215'], [' Guido', '1956']]
->>> s['a.pkl']
-['text', [<class 'str'>, <class 'map'>], {'a list': [1, 2, 3]}]
->>> s['a.json']
-{'str': 'field', 'int': 42, 'float': 3.14, 'array': [1, 2], 'nested': {'a': 1, 'b': 2}}
-
-
-
- -
-
-class py2store.misc.MiscStoreMixin(incoming_val_trans_for_key=None, outgoing_val_trans_for_key=None, dflt_incoming_val_trans=None, dflt_outgoing_val_trans=None, func_key=None)[source]
-

Mixin to transform incoming and outgoing vals according to the key their under. -Warning: If used as a subclass, this mixin should (in general) be placed before the store

-

See also: preset and postget args from wrap_kvs decorator from py2store.trans.

-
>>> # Make a class to wrap a dict with a layer that transforms written and read values
->>> class MiscStore(MiscStoreMixin, dict):
-...     def __init__(self, d,
-...                         incoming_val_trans_for_key=None, outgoing_val_trans_for_key=None,
-...                         dflt_incoming_val_trans=None, dflt_outgoing_val_trans=None,
-...                         func_key=None):
-...         dict.__init__(self, d)
-...         MiscStoreMixin.__init__(self, incoming_val_trans_for_key, outgoing_val_trans_for_key,
-...                                 dflt_incoming_val_trans, dflt_outgoing_val_trans, func_key)
-...
->>>
->>> outgoing_val_trans_for_key = dict(
-...     MiscStoreMixin._outgoing_val_trans_for_key,  # take the existing defaults...
-...     **{'.bin': lambda v: ''.join([chr(x) for x in v]).encode(), # ... override how to handle the .bin extension
-...        '.reverse_this': lambda v: v[::-1]  # add a new extension (and how to handle it)
-...     })
->>> ss = MiscStore(d={},  # store starts empty
-...                incoming_val_trans_for_key={},  # overriding incoming trans so we can see the raw data later
-...                outgoing_val_trans_for_key=outgoing_val_trans_for_key)
-...
->>> # here's what we're going to write in the store
->>> data_to_write = {
-...      'a.bin': [97, 98, 99, 49, 50, 51],
-...      'a.reverse_this': b'321cba',
-...      'a.csv': [['event', 'year'], [' Magna Carta', '1215'], [' Guido', '1956']],
-...      'a.txt': 'this is not a text',
-...      'a.pkl': ['text', [str, map], {'a list': [1, 2, 3]}],
-...      'a.json': {'str': 'field', 'int': 42, 'float': 3.14, 'array': [1, 2], 'nested': {'a': 1, 'b': 2}}}
->>> # write this data in our store
->>> for k, v in data_to_write.items():
-...     ss[k] = v
->>> list(ss)
-['a.bin', 'a.reverse_this', 'a.csv', 'a.txt', 'a.pkl', 'a.json']
->>> # Looking at the contents (what was actually stored/written)
->>> for k, v in ss.items():
-...     if k != 'a.pkl':
-...         print(f"{k}: {v}")
-...     else:  # need to verify pickle data differently, since printing contents is problematic in doctest
-...         assert pickle.loads(v) == data_to_write['a.pkl']
-a.bin: b'abc123'
-a.reverse_this: b'abc123'
-a.csv: b'event,year\r\n Magna Carta,1215\r\n Guido,1956\r\n'
-a.txt: b'this is not a text'
-a.json: b'{"str": "field", "int": 42, "float": 3.14, "array": [1, 2], "nested": {"a": 1, "b": 2}}'
-
-
-
- -
-
-py2store.misc.get_obj(k, store=<py2store.persisters.local_files.PathFormatPersister object>, incoming_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function <lambda>>, '.gz': <function decompress>, '.gzip': <function decompress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>, '.zip': <class 'py2store.slib.s_zipfile.FilesOfZip'>}, dflt_incoming_val_trans=<function identity_method>, func_key=<function <lambda>>)[source]
-

A quick way to get an object, with default… everything (but the key, you know, a clue of what you want)

-
- -
-
-py2store.misc.set_obj(k, v, store=<py2store.persisters.local_files.PathFormatPersister object>, outgoing_val_trans_for_key={'.bin': <function identity_method>, '.cnf': <function <lambda>>, '.conf': <function <lambda>>, '.config': <function <lambda>>, '.csv': <function csv_fileobj>, '.gz': <function compress>, '.gzip': <function compress>, '.ini': <function <lambda>>, '.json': <function <lambda>>, '.pickle': <function <lambda>>, '.pkl': <function <lambda>>, '.txt': <function <lambda>>}, func_key=<function <lambda>>)[source]
-

A quick way to get an object, with default… everything (but the key, you know, a clue of what you want)

-
- -
-
-

py2store.mixins

-

Forwards to dol.mixins:

-

Mixins

-
-
-

py2store.test.util

-

utils for testing

-
-
-py2store.test.util.random_dict_gen(fields=('a', 'b', 'c'), word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n: int = 100)[source]
-

Random dict (of strings) generator

-
-
Parameters
-
    -
  • fields – Field names for the random dicts

  • -
  • word_size_range – An int, 2-tuple of ints, or list-like object that defines the choices of word sizes

  • -
  • alphabet – A string or iterable defining the alphabet to draw from

  • -
  • n – The number of elements the generator will yield

  • -
-
-
Returns
-

Random dict (of strings) generator

-
-
-
- -
-
-py2store.test.util.random_formatted_str_gen(format_string='root/{}/{}_{}.test', word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n=100)[source]
-

Random formatted string generator

-
-
Parameters
-
    -
  • format_string – A format string

  • -
  • word_size_range – An int, 2-tuple of ints, or list-like object that defines the choices of word sizes

  • -
  • alphabet – A string or iterable defining the alphabet to draw from

  • -
  • n – The number of elements the generator will yield

  • -
-
-
Returns
-

Yields random strings of the format defined by format_string

-
-
-

Examples

-

# >>> list(random_formatted_str_gen(‘root/{}/{}_{}.test’, (2, 5), ‘abc’, n=5)) -[(‘root/acba/bb_abc.test’,),

-
-

(‘root/abcb/cbbc_ca.test’,), -(‘root/ac/ac_cc.test’,), -(‘root/aacc/ccbb_ab.test’,), -(‘root/aab/abb_cbab.test’,)]

-
-
>>> # The following will be made not random (by restricting the constraints to "no choice"
->>> # ... this is so that we get consistent outputs to assert for the doc test.
->>>
->>> # Example with automatic specification
->>> list(random_formatted_str_gen('root/{}/{}_{}.test', (3, 4), 'a', n=2))
-[('root/aaa/aaa_aaa.test',), ('root/aaa/aaa_aaa.test',)]
->>>
->>> # Example with manual specification
->>> list(random_formatted_str_gen('indexed field: {0}: named field: {name}', (2, 3), 'z', n=1))
-[('indexed field: zz: named field: zz',)]
-
-
-
- -
-
-py2store.test.util.random_string(length=7, alphabet='abcdefghijklmnopqrstuvwxyz')[source]
-

Same as random_word, but it optimized for strings -(5-10% faster for words of length 7, 25-30% faster for words of size 1000)

-
- -
-
-py2store.test.util.random_tuple_gen(tuple_length=3, word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n: int = 100)[source]
-

Random tuple (of strings) generator

-
-
Parameters
-
    -
  • tuple_length – The length of the tuples generated

  • -
  • word_size_range – An int, 2-tuple of ints, or list-like object that defines the choices of word sizes

  • -
  • alphabet – A string or iterable defining the alphabet to draw from

  • -
  • n – The number of elements the generator will yield

  • -
-
-
Returns
-

Random tuple (of strings) generator

-
-
-
- -
-
-py2store.test.util.random_word(length, alphabet, concat_func=<built-in function add>)[source]
-

Make a random word by concatenating randomly drawn elements from alphabet together -:param length: Length of the word -:param alphabet: Alphabet to draw from -:param concat_func: The concatenation function (e.g. + for strings and lists)

-

Note: Repeated elements in alphabet will have more chances of being drawn.

-
-
Returns
-

A word (whose type depends on what concatenating elements from alphabet produces).

-
-
-

Not making this a proper doctest because I don’t know how to seed the global random temporarily ->>> t = random_word(4, ‘abcde’); # e.g. ‘acae’ ->>> t = random_word(5, [‘a’, ‘b’, ‘c’]); # e.g. ‘cabba’ ->>> t = random_word(4, [[1, 2, 3], [40, 50], [600], [7000]]); # e.g. [40, 50, 7000, 7000, 1, 2, 3] ->>> t = random_word(4, [1, 2, 3, 4]); # e.g. 13 (because adding numbers…) ->>> # … sometimes it’s what you want: ->>> t = random_word(4, [2 ** x for x in range(8)]); # e.g. 105 (binary combination) ->>> t = random_word(4, [1, 2, 3, 4], concat_func=lambda x, y: str(x) + str(y)); # e.g. ‘4213’ ->>> t = random_word(4, [1, 2, 3, 4], concat_func=lambda x, y: int(str(x) + str(y))); # e.g. 3432

-
- -
-
-py2store.test.util.random_word_gen(word_size_range=(1, 10), alphabet='abcdefghijklmnopqrstuvwxyz', n=100)[source]
-

Random string generator -:param word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes -:param alphabet: A string or iterable defining the alphabet to draw from -:param n: The number of elements the generator will yield

-
-
Returns
-

Random string generator

-
-
-
- -
-
-

py2store.test.quick

-
-
-

py2store.test

-

test files

-
-
-

py2store.test.simple

-
-
-

py2store.test.scrap

-

scrap code

-
-
-

py2store.util

-

Forwards to dol.util:

-

General util objects

-
-
-

py2store.ext.docx

-

Simple access to docx (Word Doc) elements.

-
-
-

py2store.ext.gitlab

-

Stores to talk to gitlab, using requests.

-

Example: -``` -ogl = GitLabAccessor(base_url=”http://…”, project_name=None)

-

print(ogl.get_project_names()) # prints all project names -ogl.set_project(“PROJECT_NAME”) # sets the project to “PROJECT_NAME” -print(

-
-

ogl.get_branch_names()

-
-

) # gets the branch names of current project (as set previously) -print(

-
-

ogl.get_branch(“master”)

-
-

) # gets a json of information about the master branch of current project. -```

-
-
-

py2store.ext.hdf

-

a data object layer for HDF files

-
-
-

py2store.ext

-

py2store Extensions, Add-ons, etc. -We kept py2store purely dependency-less, using only built-ins for everything but storage system connectors.

-

That said, in order to provide the user with more power, and show him/her how py2store tools can be used to build -powerful data accessors, we provide specialized modules that do require more than builtins. These dependencies are -not listed in the setup.py module, but we wrap their imports with informative ImportError handlers.

-
-
-

py2store.ext.matlab

-

a data object layer for matlab

-
-
-

py2store.ext.kaggle

-
-
-

py2store.ext.module_imports

-
-
-

py2store.ext.audio

-
-
-

py2store.ext.github

-

a data object layer for github

-
-
-

py2store.ext.dataframes

-

Data as pandas.DataFrame from various sources

-
-
-

py2store.access

-

Utils to load stores from store specifications. -Includes the logic to allow configurations (and defaults) to be parametrized by external environmental -variables and files.

-

Every data-sourced problem has it’s problem-relevant stores. Once you get your stores right, along with the -right access credentials, indexing, serialization, caching, filtering etc. you’d like to be able to name, save -and/or share this specification, and easily get access to it later on.

-

Here are tools to help you out.

-

There are two main key-value stores: One for configurations the user wants to reuse, and the other for the user’s -desired defaults. Both have the same structure:

-
-
    -
  • first level key: Name of the resource (should be a valid python variable name)

  • -
  • The reminder is more or less free form (until the day we lay out some schemas for this)

  • -
-
-

The system will look for the specification of user_configs and user_defaults in a json file. -The filepath to this json file can specified in environment variables

-
-

PY2STORE_CONFIGS_JSON_FILEPATH and PY2STORE_DEFAULTS_JSON_FILEPATH

-
-

respectively. -By default, they are:

-
-

~/.py2store_configs.json and ~/.py2store_defaults.json

-
-

respectively.

-
-
-py2store.access.compose(*functions)[source]
-

Make a function that is the composition of the input functions

-
- -
-
-py2store.access.dflt_func_loader(f) → callable[source]
-

Loads and returns the function referenced by f, -which could be a callable or a DOTPATH_TO_MODULE.FUNC_NAME dotpath string to one, or a pipeline of these

-
- -
-
-py2store.access.dotpath_to_func(f: (<class 'str'>, <built-in function callable>)) → callable[source]
-

Loads and returns the function referenced by f, -which could be a callable or a DOTPATH_TO_MODULE.FUNC_NAME dotpath string to one.

-
- -
-
-py2store.access.dotpath_to_obj(dotpath)[source]
-

Loads and returns the object referenced by the string DOTPATH_TO_MODULE.OBJ_NAME

-
- -
-
-py2store.access.fakit(fak, func_loader=<function dflt_func_loader>)[source]
-

Execute a fak with given f, a, k and function loader.

-

Essentially returns func_loader(f)(*a, **k)

-
-
Parameters
-
    -
  • fak – A (f, a, k) specification. Could be a tuple or a dict (with ‘f’, ‘a’, ‘k’ keys). All but f are optional.

  • -
  • func_loader – A function returning a function. This is where you specify any validation of func specification f, -and/or how to get a callable from it.

  • -
-
-
-

Returns: A python object.

-
- -
-
-py2store.access.getenv(name, default=None)[source]
-

Like os.getenv, but removes a suffix r character if present (problem with some env var systems)

-
- -
-
-

py2store.__init__

-

Your portal to many Data Object Layer goodies

-
-
-py2store.__init__.ihead(store, n=1)[source]
-

Get the first item of an iterable, or a list of the first n items

-
- -
-
-py2store.__init__.kvhead(store, n=1)[source]
-

Get the first item of a kv store, or a list of the first n items

-
- -
-
-

py2store.stores.s3_store

-

Forwards to s3dol.s3_store

-
-
-

py2store.stores.delegation_stores

-
-
-

py2store.stores.sql_w_sqlalchemy

-

Forwards to sqldol

-
-
-

py2store.stores.arangodb_store

-
-
-

py2store.stores.dropbox_store

-

Forwards to dropboxdol

-
-
-

py2store.stores.local_store

-

stores to operate on local files

-
-
-class py2store.stores.local_store.AutoMkDirsOnSetitemMixin[source]
-

A mixin that will automatically create directories on setitem, when missing.

-
- -
-
-class py2store.stores.local_store.AutoMkPathformatMixin(path_format=None, max_levels=None)[source]
-

A mixin that will choose a path_format if none given

-
- -
-
-class py2store.stores.local_store.DirStore(rootdir)[source]
-

A store for local directories. -Keys are directory names and values are subdirectory DirStores.

-
>>> from py2store import __file__
->>> import os
->>> root = os.path.dirname(__file__)
->>> s = DirStore(root)
->>> assert set(s).issuperset({'stores', 'persisters', 'serializers', 'key_mappers'})
-
-
-
- -
-
-class py2store.stores.local_store.LocalBinaryStore(path_format, max_levels=None)[source]
-

Local files store for binary data

-
- -
-
-class py2store.stores.local_store.LocalJsonStore(path_format, max_levels=None)[source]
-

Local files store for text dataData is assumed to be a JSON string, and is loaded with json.loads and dumped with json.dumps

-
- -
-
-class py2store.stores.local_store.LocalPickleStore(path_format, max_levels=None, fix_imports=True, protocol=None, pickle_encoding='ASCII', pickle_errors='strict', **open_kwargs)[source]
-

Local files store with pickle serialization

-
- -
-
-py2store.stores.local_store.LocalStore
-

alias of py2store.stores.local_store.QuickPickleStore

-
- -
-
-class py2store.stores.local_store.LocalTextStore(path_format, max_levels=None)[source]
-

Local files store for text data

-
- -
-
-class py2store.stores.local_store.MakeMissingDirsStoreMixin[source]
-

Will make a local file store automatically create the directories needed to create a file. -Should be placed before the concrete perisister in the mro but in such a manner so that it receives full paths.

-
- -
-
-class py2store.stores.local_store.PathFormatStore(path_format, max_levels: int = inf, mode='', **open_kwargs)[source]
-

Local file store using templated relative paths.

-
>>> from tempfile import gettempdir
->>> import os
->>>
->>> def write_to_key(fullpath_of_relative_path, relative_path, content):  # a function to write content in files
-...    with open(fullpath_of_relative_path(relative_path), 'w') as fp:
-...        fp.write(content)
->>>
->>> # Preparation: Make a temporary rootdir and write two files in it
->>> rootdir = os.path.join(gettempdir(), 'path_format_store_test' + os.sep)
->>> if not os.path.isdir(rootdir):
-...     os.mkdir(rootdir)
->>> # recreate directory (remove existing files, delete directory, and re-create it)
->>> for f in os.listdir(rootdir):
-...     fullpath = os.path.join(rootdir, f)
-...     if os.path.isfile(fullpath):
-...         os.remove(os.path.join(rootdir, f))
->>> if os.path.isdir(rootdir):
-...     os.rmdir(rootdir)
->>> if not os.path.isdir(rootdir):
-...    os.mkdir(rootdir)
->>>
->>> filepath_of = lambda p: os.path.join(rootdir, p)  # a function to get a fullpath from a relative one
->>> # and make two files in this new dir, with some content
->>> write_to_key(filepath_of, 'a', 'foo')
->>> write_to_key(filepath_of, 'b', 'bar')
->>>
->>> # point the obj source to the rootdir
->>> s = PathFormatStore(path_format=rootdir)
->>>
->>> # assert things...
->>> assert s._prefix == rootdir  # the _rootdir is the one given in constructor
->>> assert s[filepath_of('a')] == 'foo'  # (the filepath for) 'a' contains 'foo'
->>>
->>> # two files under rootdir (as long as the OS didn't create it's own under the hood)
->>> len(s)
-2
->>> assert list(s) == [filepath_of('a'), filepath_of('b')]  # there's two files in s
->>> filepath_of('a') in s  # rootdir/a is in s
-True
->>> filepath_of('not_there') in s  # rootdir/not_there is not in s
-False
->>> filepath_of('not_there') not in s  # rootdir/not_there is not in s
-True
->>> assert list(s.keys()) == [filepath_of('a'), filepath_of('b')]  # the keys (filepaths) of s
->>> sorted(list(s.values())) # the values of s (contents of files)
-['bar', 'foo']
->>> assert list(s.items()) == [(filepath_of('a'), 'foo'), (filepath_of('b'), 'bar')]  # the (path, content) items
->>> assert s.get('this key is not there', None) is None  # trying to get the val of a non-existing key returns None
->>> s.get('this key is not there', 'some default value')  # ... or whatever you say
-'some default value'
->>>
->>> # add more files to the same folder
->>> write_to_key(filepath_of, 'this.txt', 'this')
->>> write_to_key(filepath_of, 'that.txt', 'blah')
->>> write_to_key(filepath_of, 'the_other.txt', 'bloo')
->>> # see that you now have 5 files
->>> len(s)
-5
->>> # and these files contain values:
->>> sorted(s.values())
-['bar', 'blah', 'bloo', 'foo', 'this']
->>>
->>> # but if we make an obj source to only take files whose extension is '.txt'...
->>> s = PathFormatStore(path_format=rootdir + '{}.txt')
->>>
->>> rootdir_2 = os.path.join(gettempdir(), 'obj_source_test_2') # get another rootdir
->>> if not os.path.isdir(rootdir_2):
-...    os.mkdir(rootdir_2)
->>> filepath_of_2 = lambda p: os.path.join(rootdir_2, p)
->>> # and make two files in this new dir, with some content
->>> write_to_key(filepath_of, 'this.txt', 'this')
->>> write_to_key(filepath_of, 'that.txt', 'blah')
->>> write_to_key(filepath_of, 'the_other.txt', 'bloo')
->>>
->>> ss = PathFormatStore(path_format=rootdir_2 + '{}.txt')
->>>
->>> assert s != ss  # though pointing to identical content, o and oo are not equal since the paths are not equal!
-
-
-
- -
-
-class py2store.stores.local_store.PathFormatStoreWithPrefix(*args, **kwargs)[source]
-
- -
-
-py2store.stores.local_store.PickleStore
-

alias of py2store.stores.local_store.LocalPickleStore

-
- -
-
-class py2store.stores.local_store.QuickBinaryStore(path_format=None, max_levels=None)[source]
-

Local files store for binary data with default temp root and auto dir generation on write.

-
- -
-
-class py2store.stores.local_store.QuickJsonStore(path_format=None, max_levels=None)[source]
-

Local files store for text data with default temp root and auto dir generation on write.Data is assumed to be a JSON string, and is loaded with json.loads and dumped with json.dumps

-
- -
-
-class py2store.stores.local_store.QuickLocalStoreMixin(path_format=None, max_levels=None)[source]
-

A mixin that will choose a path_format if none given, -and will automatically create directories on setitem, when missing.

-
- -
-
-class py2store.stores.local_store.QuickPickleStore(path_format=None, max_levels=None)[source]
-

Local files store with pickle serialization with default temp root and auto dir generation on write.

-
- -
-
-py2store.stores.local_store.QuickStore
-

alias of py2store.stores.local_store.QuickPickleStore

-
- -
-
-class py2store.stores.local_store.QuickTextStore(path_format=None, max_levels=None)[source]
-

Local files store for text data with default temp root and auto dir generation on write.

-
- -
-
-class py2store.stores.local_store.RelativeDirPathFormatKeys(*args, **kwargs)[source]
-
- -
-
-class py2store.stores.local_store.RelativePathFormatStore2(*args, **kwargs)[source]
-
- -
-
-

py2store.stores

-

a package of various stores

-
-
-

py2store.stores.couchdb_store

-
-
-

py2store.stores.mongo_store

-
-
-

py2store.core

-

Forwards to dol.core:

-

Core tools

-
-
-

py2store.utils.uri_utils

-

utils to work with URIs

-
-
-py2store.utils.uri_utils.build_uri(scheme, database='', username=None, password=None, host='localhost', port=None)[source]
-

Reverse of parse_uri function. -Builds a URI string from provided params.

-
- -
-
-py2store.utils.uri_utils.parse_uri(uri)[source]
-

Parses DB URI string into a dict of params. -:param uri: string formatted as: “scheme://username:password@host:port/database” -:return: a dict with these params parsed.

-
- -
-
-

py2store.utils.explicit

-

utils to make stores based on a the input data itself

-
-
-class py2store.utils.explicit.ExplicitKeymapReader(store, key_of_id=None, id_of_key=None)[source]
-

Wrap a store (instance) so that it gets it’s keys from an explicit iterable of keys.

-
>>> s = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
->>> id_of_key = {'A': 'a', 'C': 'c'}
->>> ss = ExplicitKeymapReader(s, id_of_key=id_of_key)
->>> list(ss)
-['A', 'C']
->>> ss['C']  # will look up 'C', find 'c', and call the store on that.
-3
-
-
-
- -
-
-class py2store.utils.explicit.ExplicitKeys(key_collection: Collection)[source]
-

py2store.base.Keys implementation that gets it’s keys explicitly from a collection given at initialization time. -The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.)

-
>>> keys = ExplicitKeys(key_collection=['foo', 'bar', 'alice'])
->>> 'foo' in keys
-True
->>> 'not there' in keys
-False
->>> list(keys)
-['foo', 'bar', 'alice']
-
-
-
- -
-
-class py2store.utils.explicit.ExplicitKeysSource(key_collection: Collection, _obj_of_key: Callable)[source]
-

An object source that uses an explicit keys collection and a specified function to read contents for a key.

-
- -
-
-class py2store.utils.explicit.ExplicitKeysStore(store, key_collection)[source]
-

Wrap a store (instance) so that it gets it’s keys from an explicit iterable of keys.

-
>>> s = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
->>> list(s)
-['a', 'b', 'c', 'd']
->>> ss = ExplicitKeysStore(s, ['d', 'a'])
->>> len(ss)
-2
->>> list(ss)
-['d', 'a']
->>> list(ss.values())
-[4, 1]
->>> ss.head()
-('d', 4)
-
-
-
- -
-
-class py2store.utils.explicit.ExplicitKeysWithPrefixRelativization(key_collection, _prefix=None)[source]
-

py2store.base.Keys implementation that gets it’s keys explicitly from a collection given at initialization time. -The key_collection must be a collections.abc.Collection (such as list, tuple, set, etc.)

-
>>> from py2store.base import Store
->>> s = ExplicitKeysWithPrefixRelativization(key_collection=['/root/of/foo', '/root/of/bar', '/root/for/alice'])
->>> keys = Store(store=s)
->>> 'of/foo' in keys
-True
->>> 'not there' in keys
-False
->>> list(keys)
-['of/foo', 'of/bar', 'for/alice']
-
-
-
- -
-
-class py2store.utils.explicit.ObjReader(_obj_of_key: Callable)[source]
-

A reader that uses a specified function to get the contents for a given key.

-
>>> # define a contents_of_key that reads stuff from a dict
->>> data = {'foo': 'bar', 42: "everything"}
->>> def read_dict(k):
-...     return data[k]
->>> pr = ObjReader(_obj_of_key=read_dict)
->>> pr['foo']
-'bar'
->>> pr[42]
-'everything'
->>>
->>> # define contents_of_key that reads stuff from a file given it's path
->>> def read_file(path):
-...     with open(path) as fp:
-...         return fp.read()
->>> pr = ObjReader(_obj_of_key=read_file)
->>> file_where_this_code_is = __file__  # it should be THIS file you're reading right now!
->>> print(pr[file_where_this_code_is][62:155])  # print some characters of this file
-from collections.abc import Mapping
-from typing import Callable, Collection as CollectionType
-
-
-
- -
-
-py2store.utils.explicit.invertible_maps(mapping=None, inv_mapping=None)[source]
-

Returns two maps that are inverse of each other. -Raises an AssertionError iif both maps are None, or if the maps are not inverse of each other

-

Get a pair of invertible maps ->>> invertible_maps({1: 11, 2: 22}) -({1: 11, 2: 22}, {11: 1, 22: 2}) ->>> invertible_maps(None, {11: 1, 22: 2}) -({1: 11, 2: 22}, {11: 1, 22: 2})

-

If two maps are given and invertible, you just get them back ->>> invertible_maps({1: 11, 2: 22}, {11: 1, 22: 2}) -({1: 11, 2: 22}, {11: 1, 22: 2})

-

Or if they’re not invertible ->>> invertible_maps({1: 11, 2: 22}, {11: 1, 22: ‘ha, not what you expected!’}) -Traceback (most recent call last):

-
-

…

-
-

AssertionError: mapping and inv_mapping are not inverse of each other!

-
>>> invertible_maps(None, None)
-Traceback (most recent call last):
-  ...
-ValueError: You need to specify one or both maps
-
-
-
- -
-
-

py2store.utils.timeseries_caching

-

Tools to cache time-series data.

-
-
-class py2store.utils.timeseries_caching.RegularTimeseriesCache(data_rate=1, time_rate=1, maxlen=None)[source]
-

A type that pretends to be a (possibly very large) list, but where contents of the list are populated as they are -needed. Further, the indexing of the list can be overwritten for the convenience of the user.

-

The canonical application is where we have segments of continuous waveform indexed by utc microseconds timestamps.

-

It is convenient to be able to read segments of this waveform as if it was one big waveform (handling the -discontinuities gracefully), and have the choice of using (relative or absolute) integer indices or utc indices.

-
- -
-
-

py2store.utils.attr_dict.py.attr_dict

-
-
-

py2store.utils.attr_dict.py

-
-
-

py2store.utils.cumul_aggreg_write

-

utils for bulk writing – accumulate, aggregate and write when some condition is met

-
-
-class py2store.utils.cumul_aggreg_write.CumulAggregWrite(store, cache_to_kv=<function mk_kv_from_keygen.<locals>.aggregate>, mk_cache=<class 'list'>)[source]
-
- -
-
-class py2store.utils.cumul_aggreg_write.CumulAggregWriteKvItems(store)[source]
-
- -
-
-class py2store.utils.cumul_aggreg_write.CumulAggregWriteWithAutoFlush(store, cache_to_kv=<function mk_kv_from_keygen.<locals>.aggregate>, mk_cache=<class 'list'>, flush_cache_condition=<function condition_flush_on_every_write>)[source]
-
- -
-
-py2store.utils.cumul_aggreg_write.condition_flush_on_every_write(cache)[source]
-

Boolean function used as flush_cache_condition to anytime the cache is non-empty

-
- -
-
-py2store.utils.cumul_aggreg_write.mk_group_aggregator(item_to_kv, aggregator_op=<built-in function add>, initial=<py2store.utils.cumul_aggreg_write.NoInitial object>)[source]
-

Make a generator transforming function that will -(a) make a key for each given item, -(b) group all items according to the key

-
-
Parameters
-
    -
  • item_to_kv –

  • -
  • aggregator_op –

  • -
  • initial –

  • -
-
-
-

Returns:

-
>>> # Collect words (as a csv string), grouped by the lower case of the first letter
->>> ag = mk_group_aggregator(lambda item: (item[0].lower(), item),
-...                          aggregator_op=lambda x, y: ', '.join([x, y]))
->>> list(ag(['apple', 'bananna', 'Airplane']))
-[('a', 'apple, Airplane'), ('b', 'bananna')]
->>> # Collect (and concatinate)  characters according to their ascii value modulo 3
->>> ag = mk_group_aggregator(lambda item: (item['age'], item['thing']),
-...                          aggregator_op=lambda x, y: x + [y],
-...                          initial=[])
->>> list(ag([{'age': 0, 'thing': 'new'}, {'age': 42, 'thing': 'every'}, {'age': 0, 'thing': 'just born'}]))
-[(0, ['new', 'just born']), (42, ['every'])]
-
-
-
- -
-
-py2store.utils.cumul_aggreg_write.mk_group_aggregator_with_key_func(item_to_key, aggregator_op=<built-in function add>, initial=<py2store.utils.cumul_aggreg_write.NoInitial object>)[source]
-

Make a generator transforming function that will -(a) make a key for each given item, -(b) group all items according to the key

-
-
Parameters
-
    -
  • item_to_key – Function that takes an item of the generator and outputs the key that should be used to group items

  • -
  • aggregator_op – The aggregation binary function that is used to aggregate two items together. -The function is used as is by the functools.reduce, applied to the sequence of items that were collected for -a given group

  • -
  • initial – The “empty” element to start the reduce (aggregation) with, if necessary.

  • -
-
-
-

Returns:

-
>>> # Collect words (as a csv string), grouped by the lower case of the first letter
->>> ag = mk_group_aggregator_with_key_func(lambda item: item[0].lower(),
-...                          aggregator_op=lambda x, y: ', '.join([x, y]))
->>> list(ag(['apple', 'bananna', 'Airplane']))
-[('a', 'apple, Airplane'), ('b', 'bananna')]
->>>
->>> # Collect (and concatenate) characters according to their ascii value modulo 3
-... ag = mk_group_aggregator_with_key_func(lambda item: (ord(item) % 3))
->>> list(ag('abcdefghijklmnop'))
-[(1, 'adgjmp'), (2, 'behkn'), (0, 'cfilo')]
->>>
->>> # sum all even and odd number separately
-... ag = mk_group_aggregator_with_key_func(lambda item: (item % 2))
->>> list(ag([1, 2, 3, 4, 5]))  # sum of evens is 6, and sum of odds is 9
-[(1, 9), (0, 6)]
->>>
->>> # if we wanted to collect all odds and evens, we'd need a different aggregator and initial
-... ag = mk_group_aggregator_with_key_func(lambda item: (item % 2), aggregator_op=lambda x, y: x + [y], initial=[])
->>> list(ag([1, 2, 3, 4, 5]))
-[(1, [1, 3, 5]), (0, [2, 4])]
-
-
-
- -
-
-

py2store.utils

-

general utils

-
-
-

py2store.utils.cache_descriptors

-

descriptors to cache data

-
-
-py2store.utils.cache_descriptors.CachedProperty(*args)[source]
-

CachedProperties. -This is usable directly as a decorator when given names, or when not. Any of these patterns -will work: -* @CachedProperty -* @CachedProperty() -* @CachedProperty('n','n2') -* def thing(self: …; thing = CachedProperty(thing) -* def thing(self: …; thing = CachedProperty(thing, ‘n’)

-
- -
-
-class py2store.utils.cache_descriptors.Lazy(func, name=None)[source]
-

Lazy Attributes.

-
- -
-
-class py2store.utils.cache_descriptors.cachedIn(attribute_name)[source]
-

Cached property with given cache attribute.

-
- -
-
-

py2store.utils.appendable

-

utils to make add append and extend functionality to KV stores

-
-
-

py2store.utils.affine_conversion

-

utils to carry out affine transformations (of indices)

-
-
-class py2store.utils.affine_conversion.AffineConverter(scale=1.0, offset=0.0)[source]
-

Getting a callable that will perform an affine conversion. -Note, it does it as

-
-

(val - offset) * scale

-
-

(Note slope-intercept style (though there is the .from_slope_and_intercept constructor method for that)

-
-
Inverse is available through the inv method, performing:

val / scale + offset

-
-
-
>>> convert = AffineConverter(scale=0.5, offset=1)
->>> convert(0)
--0.5
->>> convert(10)
-4.5
->>> convert.inv(4)
-9.0
->>> convert.inv(4.5)
-10.0
-
-
-
- -
-
-py2store.utils.affine_conversion.get_affine_converter_and_inverse(scale=1, offset=0, source_type_cast=None, target_type_cast=None)[source]
-
-
Getting two affine functions with given scale and offset, that are inverse of each other. Namely (for input val):

(val - offset) * scale and val / scale + offset

-
-
-

Note this is not “slope intercept” style!!

-

The source_type_cast and target_type_case (optional), allow the user to specify if these transformations need to -be further cast to a given type. -:param scale: -:param offset: -:param source_type_cast: function to apply to input -:param target_type_cast: function to apply to output -:return: Two single val functions: affine_converter, inverse_affine_converter

-

Note: Code is a lot more complex than the basic operations it performs. The reason was a worry of efficiency since -the functions that are returned are intended to be used in long loops.

-

See also: ocore.utils.conversion.AffineConverter

-
>>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1)
->>> affine_converter(0)
--0.5
->>> affine_converter(10)
-4.5
->>> inverse_affine_converter(4)
-9.0
->>> inverse_affine_converter(4.5)
-10.0
->>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1,target_type_cast=int)
->>> affine_converter(10)
-4
-
-
-
- -
-
-

py2store.utils.signatures

-

Deprecated: Forwards to py2store.signatures

-
-
-

py2store.utils.sliceable

-

utils to add sliceable functionality to stores

-
-
-class py2store.utils.sliceable.iSliceStore(store)[source]
-

Wraps a store to make a reader that acts as if the store was a list (with integer keys, and that can be sliced). -I say “list”, but it should be noted that the behavior is more that of range, that outputs an element of the list -when keying with an integer, but returns an iterable object (a range) if sliced.

-

Here, a map object is returned when the sliceable store is sliced.

-
>>> s = {'foo': 'bar', 'hello': 'world', 'alice': 'bob'}
->>> sliceable_s = iSliceStore(s)
->>> sliceable_s[1]
-'world'
->>> list(sliceable_s[0:2])
-['bar', 'world']
->>> list(sliceable_s[-2:])
-['world', 'bob']
->>> list(sliceable_s[:-1])
-['bar', 'world']
-
-
-
- -
-
-

py2store.utils.mappify

-

Utils to wrap any object into a mapping interface

-
-
-class py2store.utils.mappify.LeafMappify(target, node_types=(<class 'dict'>, ), key_concat=<function Mappify.<lambda>>, names_of_literals=(), **kwargs)[source]
-

A dict-like interface to glom. Here, only leaf keys are taken into account.

-
>>> d = {
-...     'a': 'simple',
-...     'b': {'is': 'nested'},
-...     'c': {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]}
-... }
->>> g = LeafMappify(d)
->>>
->>> assert list(g) == ['a', 'b.is', 'c.is', 'c.and', 'c.a']
->>> assert g['a'] == 'simple'
->>> assert g['b.is'] == 'nested'
->>> assert g['c.a'] == [1, 2, 3]
->>>
->>> for k, v in g.items():
-...     print(f"{k}: {v}")
-...
-a: simple
-b.is: nested
-c.is: nested
-c.and: has
-c.a: [1, 2, 3]
-
-
-
- -
-
-class py2store.utils.mappify.Mappify(target, node_types=(<class 'dict'>, ), key_concat=<function Mappify.<lambda>>, names_of_literals=(), **kwargs)[source]
-
>>> d = {
-...     'a': 'simple',
-...     'b': {'is': 'nested'},
-...     'c': {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]}
-... }
->>> g = Mappify(d)
->>>
->>> assert list(g) == ['a', 'b.is', 'b', 'c.is', 'c.and', 'c.a', 'c']
->>> assert g['a'] == 'simple'
->>> assert g['b.is'] == 'nested'
->>> assert g['c.a'] == [1, 2, 3]
->>>
->>> for k, v in g.items():
-...     print(f"{k}: {v}")
-...
-a: simple
-b.is: nested
-b: {'is': 'nested'}
-c.is: nested
-c.and: has
-c.a: [1, 2, 3]
-c: {'is': 'nested', 'and': 'has', 'a': [1, 2, 3]}
-
-
-
- -
-
-

py2store.utils.glom

-

glom is a util to extract stuff from nested structures. -It’s one of those excellent utils that I’ve written many times, but never got quite right. -Mahmoud Hashemi got it right.

-
-
BEGIN LICENSE
-

-
-

Copyright (c) 2018, Mahmoud Hashemi

-

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.

  • -
  • The names of the contributors may not be used to endorse or -promote products derived from this software without specific -prior written permission.

  • -
-
-

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 -OWNER 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.

-
-
END LICENSE
-

-
-

Now, at the time of writing this, I’ve already transformed it to bend it to my liking. -At some point it may become something else, but I wanted there to be a trace of what my seed was. -Though I can’t promise I’ll maintain the same functionality as I transform this module, here’s -a tutorial on how to use it in it’s original form:

-
-
-

I only took the main (core) module from the glom project. -Here’s the original docs of this glom module.

-

If there was ever a Python example of “big things come in small -packages”, glom might be it.

-

The glom package has one central entrypoint, -glom.glom(). Everything else in the package revolves around that -one function.

-

A couple of conventional terms you’ll see repeated many times below:

-
    -
  • target - glom is built to work on any data, so we simply -refer to the object being accessed as the “target”

  • -
  • spec - (aka “glomspec”, short for specification) The -accompanying template used to specify the structure of the return -value.

  • -
-

Now that you know the terms, let’s take a look around glom’s powerful -semantics.

-
-
-class py2store.utils.glom.Auto(spec=None)[source]
-

Switch to Auto mode (the default)

-

TODO: this seems like it should be a sub-class of class Spec() – -if Spec() could help define the interface for new “modes” or dialects -that would also help make match mode feel less duct-taped on

-
- -
-
-class py2store.utils.glom.Call(func=None, args=None, kwargs=None)[source]
-

Call specifies when a target should be passed to a function, -func.

-

Call is similar to partial() in that -it is no more powerful than lambda or other functions, but -it is designed to be more readable, with a better repr.

-
-
Parameters
-

func (callable) – a function or other callable to be called with -the target

-
-
-

Call combines well with T to construct objects. For -instance, to generate a dict and then pass it to a constructor:

-
>>> class ExampleClass(object):
-...    def __init__(self, attr):
-...        self.attr = attr
-...
->>> target = {'attr': 3.14}
->>> glom(target, Call(ExampleClass, kwargs=T)).attr
-3.14
-
-
-

This does the same as glom(target, lambda target: -ExampleClass(**target)), but it’s easy to see which one reads -better.

-
-

Note

-

Call is mostly for functions. Use a T object -if you need to call a method.

-
-
-

Warning

-

Call has a successor with a fuller-featured API, new -in 19.3.0: the Invoke specifier type.

-
-
-
-glomit(target, scope)[source]
-

run against the current target

-
- -
- -
-
-class py2store.utils.glom.Check(spec=T, **kwargs)[source]
-

Check objects are used to make assertions about the target data, -and either pass through the data or raise exceptions if there is a -problem.

-

If any check condition fails, a CheckError is raised.

-
-
Parameters
-
    -
  • spec – a sub-spec to extract the data to which other assertions will -be checked (defaults to applying checks to the target itself)

  • -
  • type – a type or sequence of types to be checked for exact match

  • -
  • equal_to – a value to be checked for equality match (“==”)

  • -
  • validate – a callable or list of callables, each representing a -check condition. If one or more return False or raise an -exception, the Check will fail.

  • -
  • instance_of – a type or sequence of types to be checked with isinstance()

  • -
  • one_of – an iterable of values, any of which can match the target (“in”)

  • -
  • default – an optional default value to replace the value when the check fails -(if default is not specified, GlomCheckError will be raised)

  • -
-
-
-

Aside from spec, all arguments are keyword arguments. Each -argument, except for default, represent a check -condition. Multiple checks can be passed, and if all check -conditions are left unset, Check defaults to performing a basic -truthy check on the value.

-
- -
-
-exception py2store.utils.glom.CheckError(msgs, check, path)[source]
-

This GlomError subtype is raised when target data fails to -pass a Check’s specified validation.

-

An uncaught CheckError looks like this:

-
>>> target = {'a': {'b': 'c'}}
->>> glom(target, {'b': ('a.b', Check(type=int))})  
-Traceback (most recent call last):
-...
-glom.CheckError: target at path ['a.b'] failed check, got error: "expected type to be 'int', found type 'str'"
-
-
-

If the Check contains more than one condition, there may be -more than one error message. The string rendition of the -CheckError will include all messages.

-

You can also catch the CheckError and programmatically access -messages through the msgs attribute on the CheckError -instance.

-
-

Note

-

As of 2018-07-05 (glom v18.2.0), the validation subsystem is -still very new. Exact error message formatting may be enhanced -in future releases.

-
-
- -
-
-class py2store.utils.glom.Coalesce(*subspecs, **kwargs)[source]
-

Coalesce objects specify fallback behavior for a list of -subspecs.

-

Subspecs are passed as positional arguments, and keyword arguments -control defaults. Each subspec is evaluated in turn, and if none -match, a CoalesceError is raised, or a default is returned, -depending on the options used.

-
-

Note

-

This operation may seem very familar if you have experience with -SQL or even C# and others.

-
-

In practice, this fallback behavior’s simplicity is only surpassed -by its utility:

-
>>> target = {'c': 'd'}
->>> glom(target, Coalesce('a', 'b', 'c'))
-'d'
-
-
-

glom tries to get 'a' from target, but gets a -KeyError. Rather than raise a PathAccessError as usual, -glom coalesces into the next subspec, 'b'. The process -repeats until it gets to 'c', which returns our value, -'d'. If our value weren’t present, we’d see:

-
>>> target = {}
->>> glom(target, Coalesce('a', 'b'))  
-Traceback (most recent call last):
-...
-glom.CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) (at path [])
-
-
-

Same process, but because target is empty, we get a -CoalesceError. If we want to avoid an exception, and we -know which value we want by default, we can set default:

-
>>> target = {}
->>> glom(target, Coalesce('a', 'b', 'c'), default='d-fault')
-'d-fault'
-
-
-

'a', 'b', and 'c' weren’t present so we got 'd-fault'.

-
-
Parameters
-
    -
  • subspecs – One or more glommable subspecs

  • -
  • default – A value to return if no subspec results in a valid value

  • -
  • default_factory – A callable whose result will be returned as a default

  • -
  • skip – A value, tuple of values, or predicate function -representing values to ignore

  • -
  • skip_exc – An exception or tuple of exception types to catch and -move on to the next subspec. Defaults to GlomError, the -parent type of all glom runtime exceptions.

  • -
-
-
-

If all subspecs produce skipped values or exceptions, a -CoalesceError will be raised. For more examples, check out -the tutorial, which makes extensive use of Coalesce.

-
- -
-
-exception py2store.utils.glom.CoalesceError(coal_obj, skipped, path)[source]
-

This GlomError subtype is raised from within a -Coalesce spec’s processing, when none of the subspecs -match and no default is provided.

-

The exception object itself keeps track of several values which -may be useful for processing:

-
-
Parameters
-
    -
  • coal_obj (Coalesce) – The original failing spec, see -Coalesce’s docs for details.

  • -
  • skipped (list) – A list of ignored values and exceptions, in the -order that their respective subspecs appear in the original -coal_obj.

  • -
  • path – Like many GlomErrors, this exception knows the path at -which it occurred.

  • -
-
-
-
>>> target = {}
->>> glom(target, Coalesce('a', 'b'))  
-Traceback (most recent call last):
-...
-glom.CoalesceError: no valid values found. Tried ('a', 'b') and got (PathAccessError, PathAccessError) ...
-
-
-
- -
-
-class py2store.utils.glom.Fill(spec=None)[source]
-

A specifier type which switches to glom into “fill-mode”. For the -spec contained within the Fill, glom will only interpret explicit -specifier types (including T objects). Whereas the default mode -has special interpretations for each of these builtins, fill-mode -takes a lighter touch, making Fill great for “filling out” Python -literals, like tuples, dicts, sets, and lists.

-
>>> target = {'data': [0, 2, 4]}
->>> spec = Fill((T['data'][2], T['data'][0]))
->>> glom(target, spec)
-(4, 0)
-
-
-

As you can see, glom’s usual built-in tuple item chaining behavior -has switched into a simple tuple constructor.

-

(Sidenote for Lisp fans: Fill is like glom’s quasi-quoting.)

-
- -
-
-exception py2store.utils.glom.GlomError[source]
-

The base exception for all the errors that might be raised from -glom() processing logic.

-

By default, exceptions raised from within functions passed to glom -(e.g., len, sum, any lambda) will not be wrapped in a -GlomError.

-
- -
-
-class py2store.utils.glom.Glommer(**kwargs)[source]
-

All the wholesome goodness that it takes to make glom work. This -type mostly serves to encapsulate the type registration context so -that advanced uses of glom don’t need to worry about stepping on -each other’s toes.

-

Glommer objects are lightweight and, once instantiated, provide -the glom() method we know and love:

-
>>> glommer = Glommer()
->>> glommer.glom({}, 'a.b.c', default='d')
-'d'
->>> Glommer().glom({'vals': list(range(3))}, ('vals', len))
-3
-
-
-

Instances also provide register() method for -localized control over type handling.

-
-
Parameters
-

register_default_types (bool) – Whether or not to enable the -handling behaviors of the default glom(). These -default actions include dict access, list and iterable -iteration, and generic object attribute access. Defaults to -True.

-
-
-
-
-register(target_type, **kwargs)[source]
-

Register target_type so glom() will -know how to handle instances of that type as targets.

-
-
Parameters
-
    -
  • target_type (type) – A type expected to appear in a glom() -call target

  • -
  • get (callable) – A function which takes a target object and -a name, acting as a default accessor. Defaults to -getattr().

  • -
  • iterate (callable) – A function which takes a target object -and returns an iterator. Defaults to iter() if -target_type appears to be iterable.

  • -
  • exact (bool) – Whether or not to match instances of subtypes -of target_type.

  • -
-
-
-
-

Note

-

The module-level register() function affects the -module-level glom() function’s behavior. If this -global effect is undesirable for your application, or -you’re implementing a library, consider instantiating a -Glommer instance, and using the -register() and Glommer.glom() -methods instead.

-
-
- -
- -
-
-class py2store.utils.glom.Inspect(*a, **kw)[source]
-

The Inspect specifier type provides a way to get -visibility into glom’s evaluation of a specification, enabling -debugging of those tricky problems that may arise with unexpected -data.

-

Inspect can be inserted into an existing spec in one of two -ways. First, as a wrapper around the spec in question, or second, -as an argument-less placeholder wherever a spec could be.

-

Inspect supports several modes, controlled by -keyword arguments. Its default, no-argument mode, simply echos the -state of the glom at the point where it appears:

-
>>> target = {'a': {'b': {}}}
->>> val = glom(target, Inspect('a.b'))  # wrapping a spec
----
-path:   ['a.b']
-target: {'a': {'b': {}}}
-output: {}
----
-
-
-

Debugging behavior aside, Inspect has no effect on -values in the target, spec, or result.

-
-
Parameters
-
    -
  • echo (bool) – Whether to print the path, target, and output of -each inspected glom. Defaults to True.

  • -
  • recursive (bool) – Whether or not the Inspect should be applied -at every level, at or below the spec that it wraps. Defaults -to False.

  • -
  • breakpoint (bool) – This flag controls whether a debugging prompt -should appear before evaluating each inspected spec. Can also -take a callable. Defaults to False.

  • -
  • post_mortem (bool) – This flag controls whether exceptions -should be caught and interactively debugged with pdb on -inspected specs.

  • -
-
-
-

All arguments above are keyword-only to avoid overlap with a -wrapped spec.

-
-

Note

-

Just like pdb.set_trace(), be careful about leaving stray -Inspect() instances in production glom specs.

-
-
- -
-
-class py2store.utils.glom.Invoke(func)[source]
-

Specifier type designed for easy invocation of callables from glom.

-
-
Parameters
-

func (callable) – A function or other callable object.

-
-
-

Invoke is similar to functools.partial(), but with the -ability to set up a “templated” call which interleaves constants and -glom specs.

-

For example, the following creates a spec which can be used to -check if targets are integers:

-
>>> is_int = Invoke(isinstance).specs(T).constants(int)
->>> glom(5, is_int)
-True
-
-
-

And this composes like any other glom spec:

-
>>> target = [7, object(), 9]
->>> glom(target, [is_int])
-[True, False, True]
-
-
-

Another example, mixing positional and keyword arguments:

-
>>> spec = Invoke(sorted).specs(T).constants(key=int, reverse=True)
->>> target = ['10', '5', '20', '1']
->>> glom(target, spec)
-['20', '10', '5', '1']
-
-
-

Invoke also helps with evaluating zero-argument functions:

-
>>> glom(target={}, spec=Invoke(int))
-0
-
-
-

(A trivial example, but from timestamps to UUIDs, zero-arg calls do come up!)

-
-

Note

-

Invoke is mostly for functions, object construction, and callable -objects. For calling methods, consider the T object.

-
-
-
-constants(*a, **kw)[source]
-

Returns a new Invoke spec, with the provided positional -and keyword argument values stored for passing to the -underlying function.

-
>>> spec = Invoke(T).constants(5)
->>> glom(range, (spec, list))
-[0, 1, 2, 3, 4]
-
-
-

Subsequent positional arguments are appended:

-
>>> spec = Invoke(T).constants(2).constants(10, 2)
->>> glom(range, (spec, list))
-[2, 4, 6, 8]
-
-
-

Keyword arguments also work as one might expect:

-
>>> round_2 = Invoke(round).constants(ndigits=2).specs(T)
->>> glom(3.14159, round_2)
-3.14
-
-
-

constants() and other Invoke -methods may be called multiple times, just remember that every -call returns a new spec.

-
- -
-
-classmethod specfunc(spec)[source]
-

Creates an Invoke instance where the function is -indicated by a spec.

-
>>> spec = Invoke.specfunc('func').constants(5)
->>> glom({'func': range}, (spec, list))
-[0, 1, 2, 3, 4]
-
-
-
- -
-
-specs(*a, **kw)[source]
-

Returns a new Invoke spec, with the provided positional -and keyword arguments stored to be interpreted as specs, with -the results passed to the underlying function.

-
>>> spec = Invoke(range).specs('value')
->>> glom({'value': 5}, (spec, list))
-[0, 1, 2, 3, 4]
-
-
-

Subsequent positional arguments are appended:

-
>>> spec = Invoke(range).specs('start').specs('end', 'step')
->>> target = {'start': 2, 'end': 10, 'step': 2}
->>> glom(target, (spec, list))
-[2, 4, 6, 8]
-
-
-

Keyword arguments also work as one might expect:

-
>>> multiply = lambda x, y: x * y
->>> times_3 = Invoke(multiply).constants(y=3).specs(x='value')
->>> glom({'value': 5}, times_3)
-15
-
-
-

specs() and other Invoke -methods may be called multiple times, just remember that every -call returns a new spec.

-
- -
-
-star(args=None, kwargs=None)[source]
-

Returns a new Invoke spec, with args and/or kwargs -specs set to be “starred” or “star-starred” (respectively)

-
>>> import os.path
->>> spec = Invoke(os.path.join).star(args='path')
->>> target = {'path': ['path', 'to', 'dir']}
->>> glom(target, spec)
-'path/to/dir'
-
-
-
-
Parameters
-
    -
  • args (spec) – A spec to be evaluated and “starred” into the -underlying function.

  • -
  • kwargs (spec) – A spec to be evaluated and “star-starred” into -the underlying function.

  • -
-
-
-

One or both of the above arguments should be set.

-

The star(), like other Invoke -methods, may be called multiple times. The args and kwargs -will be stacked in the order in which they are provided.

-
- -
- -
-
-class py2store.utils.glom.Let(**kw)[source]
-

This specifier type assigns variables to the scope.

-
>>> target = {'data': {'val': 9}}
->>> spec = (Let(value=T['data']['val']), {'val': S['value']})
->>> glom(target, spec)
-{'val': 9}
-
-
-
- -
-
-class py2store.utils.glom.Literal(value)[source]
-

Literal objects specify literal values in rare cases when part of -the spec should not be interpreted as a glommable -subspec. Wherever a Literal object is encountered in a spec, it is -replaced with its wrapped value in the output.

-
>>> target = {'a': {'b': 'c'}}
->>> spec = {'a': 'a.b', 'readability': Literal('counts')}
->>> pprint(glom(target, spec))
-{'a': 'c', 'readability': 'counts'}
-
-
-

Instead of accessing 'counts' as a key like it did with -'a.b', glom() just unwrapped the literal and -included the value.

-

Literal takes one argument, the literal value that should appear -in the glom output.

-

This could also be achieved with a callable, e.g., lambda x: -'literal_string' in the spec, but using a Literal -object adds explicitness, code clarity, and a clean repr().

-
- -
-
-class py2store.utils.glom.Path(*path_parts)[source]
-

Path objects specify explicit paths when the default -'a.b.c'-style general access syntax won’t work or isn’t -desirable. Use this to wrap ints, datetimes, and other valid -keys, as well as strings with dots that shouldn’t be expanded.

-
>>> target = {'a': {'b': 'c', 'd.e': 'f', 2: 3}}
->>> glom(target, Path('a', 2))
-3
->>> glom(target, Path('a', 'd.e'))
-'f'
-
-
-

Paths can be used to join together other Path objects, as -well as T objects:

-
>>> Path(T['a'], T['b'])
-T['a']['b']
->>> Path(Path('a', 'b'), Path('c', 'd'))
-Path('a', 'b', 'c', 'd')
-
-
-

Paths also support indexing and slicing, with each access -returning a new Path object:

-
>>> path = Path('a', 'b', 1, 2)
->>> path[0]
-Path('a')
->>> path[-2:]
-Path(1, 2)
-
-
-
-
-from_t()[source]
-

return the same path but starting from T

-
- -
-
-classmethod from_text(text)[source]
-

Make a Path from .-delimited text:

-
>>> Path.from_text('a.b.c')
-Path('a', 'b', 'c')
-
-
-
- -
-
-items()[source]
-

Returns a tuple of (operation, value) pairs.

-
>>> Path(T.a.b, 'c', T['d']).items()
-(('.', 'a'), ('.', 'b'), ('P', 'c'), ('[', 'd'))
-
-
-
- -
-
-values()[source]
-

Returns a tuple of values referenced in this path.

-
>>> Path(T.a.b, 'c', T['d']).values()
-('a', 'b', 'c', 'd')
-
-
-
- -
- -
-
-exception py2store.utils.glom.PathAccessError(exc, path, part_idx)[source]
-

This GlomError subtype represents a failure to access an -attribute as dictated by the spec. The most commonly-seen error -when using glom, it maintains a copy of the original exception and -produces a readable error message for easy debugging.

-

If you see this error, you may want to:

-
-
    -
  • Check the target data is accurate using Inspect

  • -
  • Catch the exception and return a semantically meaningful error message

  • -
  • Use glom.Coalesce to specify a default

  • -
  • Use the top-level default kwarg on glom()

  • -
-
-

In any case, be glad you got this error and not the one it was -wrapping!

-
-
Parameters
-
    -
  • exc (Exception) – The error that arose when we tried to access -path. Typically an instance of KeyError, AttributeError, -IndexError, or TypeError, and sometimes others.

  • -
  • path (Path) – The full Path glom was in the middle of accessing -when the error occurred.

  • -
  • part_idx (int) – The index of the part of the path that caused -the error.

  • -
-
-
-
>>> target = {'a': {'b': None}}
->>> glom(target, 'a.b.c')  
-Traceback (most recent call last):
-...
-glom.PathAccessError: could not access 'c', part 2 of Path('a', 'b', 'c'), got error: ...
-
-
-
- -
-
-class py2store.utils.glom.Spec(spec, scope=None)[source]
-

Spec objects serve three purposes, here they are, roughly ordered -by utility:

-
-
    -
  1. As a form of compiled or “curried” glom call, similar to -Python’s built-in re.compile().

  2. -
  3. A marker as an object as representing a spec rather than a -literal value in certain cases where that might be ambiguous.

  4. -
  5. A way to update the scope within another Spec.

  6. -
-
-

In the second usage, Spec objects are the complement to -Literal, wrapping a value and marking that it -should be interpreted as a glom spec, rather than a literal value. -This is useful in places where it would be interpreted as a value -by default. (Such as T[key], Call(func) where key and func are -assumed to be literal values and not specs.)

-
-
Parameters
-
    -
  • spec – The glom spec.

  • -
  • scope (dict) – additional values to add to the scope when -evaluating this Spec

  • -
-
-
-
- -
-
-class py2store.utils.glom.TType[source]
-

T, short for “target”. A singleton object that enables -object-oriented expression of a glom specification.

-
-

Note

-

T is a singleton, and does not need to be constructed.

-
-

Basically, think of T as your data’s stunt double. Everything -that you do to T will be recorded and executed during the -glom() call. Take this example:

-
>>> spec = T['a']['b']['c']
->>> target = {'a': {'b': {'c': 'd'}}}
->>> glom(target, spec)
-'d'
-
-
-

So far, we’ve relied on the 'a.b.c'-style shorthand for -access, or used the Path objects, but if you want -to explicitly do attribute and key lookups, look no further than -T.

-

But T doesn’t stop with unambiguous access. You can also call -methods and perform almost any action you would with a normal -object:

-
>>> spec = ('a', (T['b'].items(), list))  # reviewed below
->>> glom(target, spec)
-[('c', 'd')]
-
-
-

A T object can go anywhere in the spec. As seen in the example -above, we access 'a', use a T to get 'b' and iterate -over its items, turning them into a list.

-

You can even use T with Call to construct objects:

-
>>> class ExampleClass(object):
-...    def __init__(self, attr):
-...        self.attr = attr
-...
->>> target = {'attr': 3.14}
->>> glom(target, Call(ExampleClass, kwargs=T)).attr
-3.14
-
-
-

On a further note, while lambda works great in glom specs, and -can be very handy at times, T and Call -eliminate the need for the vast majority of lambda usage with -glom.

-

Unlike lambda and other functions, T roundtrips -beautifully and transparently:

-
>>> T['a'].b['c']('success')
-T['a'].b['c']('success')
-
-
-

T-related access errors raise a PathAccessError -during the glom() call.

-
-

Note

-

While T is clearly useful, powerful, and here to stay, its -semantics are still being refined. Currently, operations beyond -method calls and attribute/item access are considered -experimental and should not be relied upon.

-
-
- -
-
-class py2store.utils.glom.TargetRegistry(register_default_types=True)[source]
-

responsible for registration of target types for iteration -and attribute walking

-
-
-get_handler(op, obj, path=None, raise_exc=True)[source]
-

for an operation and object instance, obj, return the -closest-matching handler function, raising UnregisteredTarget -if no handler can be found for obj (or False if -raise_exc=False)

-
- -
-
-register_op(op_name, auto_func=None, exact=False)[source]
-

add operations beyond the builtins (‘get’ and ‘iterate’ at the time -of writing).

-

auto_func is a function that when passed a type, returns a -handler associated with op_name if it’s supported, or False if -it’s not.

-

See glom.core.register_op() for the global version used by -extensions.

-
- -
- -
-
-exception py2store.utils.glom.UnregisteredTarget(op, target_type, type_map, path)[source]
-

This GlomError subtype is raised when a spec calls for an -unsupported action on a target type. For instance, trying to -iterate on an non-iterable target:

-
>>> glom(object(), ['a.b.c'])  
-Traceback (most recent call last):
-...
-glom.UnregisteredTarget: target type 'object' not registered for 'iterate', expected one of registered types: (...)
-
-
-

It should be noted that this is a pretty uncommon occurrence in -production glom usage. See the setup-and-registration -section for details on how to avoid this error.

-

An UnregisteredTarget takes and tracks a few values:

-
-
Parameters
-
    -
  • op (str) – The name of the operation being performed (‘get’ or ‘iterate’)

  • -
  • target_type (type) – The type of the target being processed.

  • -
  • type_map (dict) – A mapping of target types that do support this operation

  • -
  • path – The path at which the error occurred.

  • -
-
-
-
- -
-
-py2store.utils.glom.glom(target, spec, **kwargs)[source]
-

Access or construct a value from a given target based on the -specification declared by spec.

-

Accessing nested data, aka deep-get:

-
>>> target = {'a': {'b': 'c'}}
->>> glom(target, 'a.b')
-'c'
-
-
-

Here the spec was just a string denoting a path, -'a.b.. As simple as it should be. The next example shows -how to use nested data to access many fields at once, and make -a new nested structure.

-

Constructing, or restructuring more-complicated nested data:

-
>>> target = {'a': {'b': 'c', 'd': 'e'}, 'f': 'g', 'h': [0, 1, 2]}
->>> spec = {'a': 'a.b', 'd': 'a.d', 'h': ('h', [lambda x: x * 2])}
->>> output = glom(target, spec)
->>> pprint(output)
-{'a': 'c', 'd': 'e', 'h': [0, 2, 4]}
-
-
-

glom also takes a keyword-argument, default. When set, -if a glom operation fails with a GlomError, the -default will be returned, very much like -dict.get():

-
>>> glom(target, 'a.xx', default='nada')
-'nada'
-
-
-

The skip_exc keyword argument controls which errors should -be ignored.

-
>>> glom({}, lambda x: 100.0 / len(x), default=0.0, skip_exc=ZeroDivisionError)
-0.0
-
-
-
-
Parameters
-
    -
  • target (object) – the object on which the glom will operate.

  • -
  • spec (object) – Specification of the output object in the form -of a dict, list, tuple, string, other glom construct, or -any composition of these.

  • -
  • default (object) – An optional default to return in the case -an exception, specified by skip_exc, is raised.

  • -
  • skip_exc (Exception) – An optional exception or tuple of -exceptions to ignore and return default (None if -omitted). If skip_exc and default are both not set, -glom raises errors through.

  • -
  • scope (dict) – Additional data that can be accessed -via S inside the glom-spec.

  • -
-
-
-

It’s a small API with big functionality, and glom’s power is -only surpassed by its intuitiveness. Give it a whirl!

-
- -
-
-py2store.utils.glom.is_iterable(x)[source]
-

Similar in nature to callable(), is_iterable returns -True if an object is `iterable`_, False if not. ->>> is_iterable([]) -True ->>> is_iterable(1) -False

-
- -
-
-py2store.utils.glom.make_sentinel(name='_MISSING', var_name=None)[source]
-

Creates and returns a new instance of a new class, suitable for -usage as a “sentinel”, a kind of singleton often used to indicate -a value is missing when None is a valid input.

-
-
Parameters
-
    -
  • name (str) – Name of the Sentinel

  • -
  • var_name (str) – Set this name to the name of the variable in -its respective module enable pickleability.

  • -
-
-
-
>>> make_sentinel(var_name='_MISSING')
-_MISSING
-
-
-

The most common use cases here in boltons are as default values -for optional function arguments, partly because of its -less-confusing appearance in automatically generated -documentation. Sentinels also function well as placeholders in queues -and linked lists.

-
-

Note

-

By design, additional calls to make_sentinel with the same -values will not produce equivalent objects.

-
>>> make_sentinel('TEST') == make_sentinel('TEST')
-False
->>> type(make_sentinel('TEST')) == type(make_sentinel('TEST'))
-False
-
-
-
-
- -
-
-py2store.utils.glom.register(target_type, **kwargs)[source]
-

Register target_type so glom() will -know how to handle instances of that type as targets.

-
-
Parameters
-
    -
  • target_type (type) – A type expected to appear in a glom() -call target

  • -
  • get (callable) – A function which takes a target object and -a name, acting as a default accessor. Defaults to -getattr().

  • -
  • iterate (callable) – A function which takes a target object -and returns an iterator. Defaults to iter() if -target_type appears to be iterable.

  • -
  • exact (bool) – Whether or not to match instances of subtypes -of target_type.

  • -
-
-
-
-

Note

-

The module-level register() function affects the -module-level glom() function’s behavior. If this -global effect is undesirable for your application, or -you’re implementing a library, consider instantiating a -Glommer instance, and using the -register() and Glommer.glom() -methods instead.

-
-
- -
-
-py2store.utils.glom.register_op(op_name, **kwargs)[source]
-

For extension authors needing to add operations beyond the builtin -‘get’ and ‘iterate’ to the default scope. See TargetRegistry for more details.

-
- -
-
-

py2store.persisters.sql_w_odbc

-
-
-

py2store.persisters.dynamodb_w_boto3

-
-
-

py2store.persisters.couchdb_w_couchdb

-
-
-

py2store.persisters.ftp_persister

-
-
-

py2store.persisters.dropbox_w_urllib

-
-
-

py2store.persisters._google_drive_in_progress

-
-
-

py2store.persisters.dropbox_w_dropbox

-

Forwards to dropboxdol

-
-
-

py2store.persisters.redis_w_redis

-

Forwards to redisdol

-
-
-

py2store.persisters.sql_w_sqlalchemy

-

Forwards to sqldol

-
-
-

py2store.persisters.new_s3

-

Forwards to s3dol.new_s3

-
-
-

py2store.persisters

-

base persisters – now all forwarding to separate libraries

-
-
-

py2store.persisters.dropbox_w_requests

-
-
-

py2store.persisters.w_aiofile

-

Forwards to aiofiledol

-
-
-

py2store.persisters.local_files

-

base classes to work with local files

-
-
-class py2store.persisters.local_files.DirReader(rootdir)[source]
-

KV Reader whose keys (AND VALUES) are directory full paths of the subdirectories of rootdir.

-
- -
-
-class py2store.persisters.local_files.DirpathFormatKeys(path_format: str, max_levels: int = inf)[source]
-
- -
-
-class py2store.persisters.local_files.FileReader(rootdir)[source]
-

KV Reader whose keys are paths and values are: -- Another FileReader if a path points to a directory -- The bytes of the file if the path points to a file.

-
- -
-
-class py2store.persisters.local_files.FilepathFormatKeys(path_format: str, max_levels: int = inf)[source]
-
- -
-
-exception py2store.persisters.local_files.FolderNotFoundError[source]
-
- -
-
-class py2store.persisters.local_files.LocalFileRWD(mode='', **open_kwargs)[source]
-

A class providing get, set and delete functionality using local files as the storage backend.

-
- -
-
-class py2store.persisters.local_files.LocalFileStreamGetter(**open_kwargs)[source]
-

A class to get stream objects of local open files. -The class can only get keys, and only to read, write (destructive or append).

-
>>> from tempfile import mkdtemp
->>> import os
->>> rootdir = mkdtemp()
->>>
->>> appendable_stream = LocalFileStreamGetter(mode='a+')
->>> reader = PathFormatPersister(rootdir)
->>> filepath = os.path.join(rootdir, 'tmp.txt')
->>>
->>> with appendable_stream[filepath] as fp:
-...     fp.write('hello')
-5
->>> print(reader[filepath])
-hello
->>> with appendable_stream[filepath] as fp:
-...     fp.write(' world')
-6
->>>
->>> print(reader[filepath])
-hello world
-
-
-
- -
-
-class py2store.persisters.local_files.PathFormatPersister(path_format, max_levels: int = inf, mode='', **open_kwargs)[source]
-
- -
-
-class py2store.persisters.local_files.PrefixedDirpathsRecursive[source]
-

Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. -This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)).

-
- -
-
-class py2store.persisters.local_files.PrefixedFilepaths[source]
-

Keys collection for local files, where the keys are full filepaths DIRECTLY under a given root dir _prefix. -This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)).

-
- -
-
-class py2store.persisters.local_files.PrefixedFilepathsRecursive[source]
-

Keys collection for local files, where the keys are full filepaths RECURSIVELY under a given root dir _prefix. -This mixin adds iteration (__iter__), length (__len__), and containment (__contains__(k)).

-
- -
-
-py2store.persisters.local_files.ensure_slash_suffix(path: str)[source]
-

Add a file separation (/ or ) at the end of path str, if not already present.

-
- -
-
-

py2store.persisters.arangodb_w_pyarango

-
-
-

py2store.persisters._cassandra_in_progress

-
-
-

py2store.persisters._couchdb_in_progress

-
-
-

py2store.persisters.s3_w_boto3

-

Forwards to s3dol.s3_w_boto3

-
-
-

py2store.persisters._postgres_w_psycopg2_in_progress

-
-
-

py2store.persisters.ssh_persister

-
-
-

py2store.persisters.mongo_w_pymongo

-
-
-

py2store.persisters.googledrive_w_pydrive

-

Forwards to pydrivedol

-
-
-

py2store.sources

-

Forwards to dol.sources:

-

This module contains key-value views of disparate sources.

-
-
-

py2store.dig

-

Forwards to dol.dig:

-

Layers introspection

-
-
-

py2store.serializers.pickled

-

functions to pickle objects

-
-
-py2store.serializers.pickled.mk_marshal_rw_funcs(**kwargs)[source]
-

Generates a reader and writer using marshal. That is, a pair of parametrized loads and dumps

-
>>> read, write = mk_marshal_rw_funcs()
->>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2]}}
->>> serialized_d = write(d)
->>> deserialized_d = read(serialized_d)
->>> assert d == deserialized_d
-
-
-
- -
-
-py2store.serializers.pickled.mk_pickle_rw_funcs(fix_imports=True, protocol=None, pickle_encoding='ASCII', pickle_errors='strict')[source]
-

Generates a reader and writer using pickle. That is, a pair of parametrized loads and dumps

-
>>> read, write = mk_pickle_rw_funcs()
->>> d = {'a': 'simple', 'and': {'a': b'more', 'complex': [1, 2.2, dict]}}
->>> serialized_d = write(d)
->>> deserialized_d = read(serialized_d)
->>> assert d == deserialized_d
-
-
-
- -
-
-

py2store.serializers.jsonization

-
-
-

py2store.serializers

-

a package of serializers

-
-
-

py2store.serializers.sequential

-
-
-

py2store.serializers.regular_panel_data

-
-
-

py2store.serializers.audio

-
-
-

py2store.caching

-

Forwards to dol.caching:

-

Tools to add caching layers to stores.

-
-
-

py2store.scrap

-
-
-

py2store.scrap.new_gen_local

-
-
-

py2store.examples.write_caches

-

stores that implement various write caching algorithms

-
-
-py2store.examples.write_caches.timestamp_on_cache_and_concatenate_all_values()[source]
-

The cache timestamps (with system clock) every item on insertion (append) and uses the min timestamp as -a key for storage.

-
- -
-
-

py2store.examples

-

modules demoing various uses of py2store

-
-
-

py2store.examples.python_code_stats

-

Note: Moved to umpyre (pip install umpyre)

-

Get stats about packages. Your own, or other’s. -Things like…

-

# >>> import collections -# >>> modules_info_df(collections) -# lines empty_lines … num_of_functions num_of_classes -# collections.__init__ 1273 189 … 1 9 -# collections.abc 3 1 … 0 25 -# <BLANKLINE> -# [2 rows x 7 columns] -# >>> modules_info_df_stats(collections.abc) -# lines 1276.000000 -# empty_lines 190.000000 -# comment_lines 73.000000 -# docs_lines 133.000000 -# function_lines 138.000000 -# num_of_functions 1.000000 -# num_of_classes 34.000000 -# empty_lines_ratio 0.148903 -# comment_lines_ratio 0.057210 -# function_lines_ratio 0.108150 -# mean_lines_per_function 138.000000 -# dtype: float64 -# >>> stats_of([‘urllib’, ‘json’, ‘collections’]) -# urllib json collections -# empty_lines_ratio 0.157034 0.136818 0.148903 -# comment_lines_ratio 0.074142 0.038432 0.057210 -# function_lines_ratio 0.213907 0.449654 0.108150 -# mean_lines_per_function 13.463768 41.785714 138.000000 -# lines 4343.000000 1301.000000 1276.000000 -# empty_lines 682.000000 178.000000 190.000000 -# comment_lines 322.000000 50.000000 73.000000 -# docs_lines 425.000000 218.000000 133.000000 -# function_lines 929.000000 585.000000 138.000000 -# num_of_functions 69.000000 14.000000 1.000000 -# num_of_classes 55.000000 3.000000 34.000000

-
-
-

py2store.examples.kv_walking

-

walking through kv stores

-
-
-class py2store.examples.kv_walking.SrcReader(src, src_to_keys, key_to_obj)[source]
-
-
-update_keys_cache(keys)
-

Updates the _keys_cache by calling its {} method

-
- -
- -
-
-py2store.examples.kv_walking.conjunction(*args, **kwargs)[source]
-

` -will be equal to -` -func_1(*args, **kwargs) & … & func_n(*args, **kwargs) -``` -for all args, kwargs.

-
- -
-
-py2store.examples.kv_walking.kv_walk(v: collections.abc.Mapping, yield_func=<function asis>, walk_filt=<function val_is_mapping>, pkv_to_pv=<function tuple_keypath_and_val>, p=())[source]
-
-
Parameters
-
    -
  • v –

  • -
  • yield_func – (pp, k, vv) -> what ever you want the gen to yield

  • -
  • walk_filt – (p, k, vv) -> (bool) whether to explore the nested structure v further

  • -
  • pkv_to_pv – (p, k, v) -> (pp, vv) -where pp is a form of p + k (update of the path with the new node k) -and vv is the value that will be used by both walk_filt and yield_func

  • -
  • p – The path to v

  • -
-
-
-
>>> d = {'a': 1, 'b': {'c': 2, 'd': 3}}
->>> list(kv_walk(d))
-[(('a',), 'a', 1), (('b',), 'b', {'c': 2, 'd': 3}), (('b', 'c'), 'c', 2), (('b', 'd'), 'd', 3)]
->>> list(kv_walk(d, lambda p, k, v: '.'.join(p)))
-['a', 'b', 'b.c', 'b.d']
->>> list(kv_walk(d, lambda p, k, v: '.'.join(p)))
-['a', 'b', 'b.c', 'b.d']
-
-
-
- -
-
-

py2store.my

-

functionalities meant to be configurable

-
-
-

py2store.my.grabbers

-

define stores (and functions) so they give you data as you want it, depending on the extension

-
-
-

py2store.trans

-

Forwards to dol.trans:

-

Transformation/wrapping tools

-
-
-

py2store.key_mappers.str_utils

-

utils from strings

-
-
-py2store.key_mappers.str_utils.args_and_kwargs_indices(format_string)[source]
-

Get the sets of indices and names used in manual specification of format strings, or None, None if auto spec. -:param format_string: A format string (i.e. a string with {…} to mark parameter placement and formatting

-
-
Returns
-

None, None if format_string is an automatic specification -set_of_indices_used, set_of_fields_used if it is a manual specification

-
-
-
>>> format_string = '{0} (no 1) {2}, {see} this, {0} is a duplicate (appeared before) and {name} is string-named'
->>> assert args_and_kwargs_indices(format_string) == ({0, 2}, {'name', 'see'})
->>> format_string = 'This is a format string with only automatic field specification: {}, {}, {} etc.'
->>> assert args_and_kwargs_indices(format_string) == (set(), set())
-
-
-
- -
-
-py2store.key_mappers.str_utils.auto_field_format_str(format_str)[source]
-

Get an auto field version of the format_str

-
-
Parameters
-

format_str – A format string

-
-
Returns
-

A transformed format_str that has no names {inside} {formatting} {braces}.

-
-
-
>>> auto_field_format_str('R/{0}/{one}/{}/{two}/T')
-'R/{}/{}/{}/{}/T'
-
-
-
- -
-
-py2store.key_mappers.str_utils.compile_str_from_parsed(parsed)[source]
-

The (quasi-)inverse of string.Formatter.parse.

-
-
Parameters
-
    -
  • parsed – iterator of (literal_text, field_name, format_spec, conversion) tuples,

  • -
  • yield by string.Formatter.parse (as) –

  • -
-
-
Returns
-

A format string that would produce such a parsed input.

-
-
-
>>> s =  "ROOT/{}/{0!r}/{1!i:format}/hello{:0.02f}TAIL"
->>> assert compile_str_from_parsed(string.Formatter().parse(s)) == s
->>>
->>> # Or, if you want to see more details...
->>> parsed = list(string.Formatter().parse(s))
->>> for p in parsed:
-...     print(p)
-('ROOT/', '', '', None)
-('/', '0', '', 'r')
-('/', '1', 'format', 'i')
-('/hello', '', '0.02f', None)
-('TAIL', None, None, None)
->>> compile_str_from_parsed(parsed)
-'ROOT/{}/{0!r}/{1!i:format}/hello{:0.02f}TAIL'
-
-
-
- -
-
-py2store.key_mappers.str_utils.format_params_in_str_format(format_string)[source]
-

Get the “parameter” indices/names of the format_string

-
-
Parameters
-

format_string – A format string (i.e. a string with {…} to mark parameter placement and formatting

-
-
Returns
-

A list of parameter indices used in the format string, in the order they appear, with repetition. -Parameter indices could be integers, strings, or None (to denote “automatic field numbering”.

-
-
-
>>> format_string = '{0} (no 1) {2}, and {0} is a duplicate, {} is unnamed and {name} is string-named'
->>> format_params_in_str_format(format_string)
-[0, 2, 0, None, 'name']
-
-
-
- -
-
-py2store.key_mappers.str_utils.get_explicit_positions(parsed_str_format)[source]
-
>>> parsed = parse_str_format("all/{}/is/{2}/position/{except}{this}{0}")
->>> get_explicit_positions(parsed)
-{0, 2}
-
-
-
- -
-
-py2store.key_mappers.str_utils.is_automatic_format_params(format_params)[source]
-

Says if the format_params is from an automatic specification -See Also: is_manual_format_params and is_hybrid_format_params

-
- -
-
-py2store.key_mappers.str_utils.is_automatic_format_string(format_string)[source]
-

Says if the format_string is uses automatic specification -See Also: is_manual_format_params ->>> is_automatic_format_string(‘Manual: indices: {1} {2}, named: {named} {fields}’) -False ->>> is_automatic_format_string(‘Auto: only un-indexed and un-named: {} {}…’) -True ->>> is_automatic_format_string(‘Hybrid: at least a {}, and a {0} or a {name}’) -False ->>> is_manual_format_string(‘No formatting is both manual and automatic formatting!’) -True

-
- -
-
-py2store.key_mappers.str_utils.is_hybrid_format_params(format_params)[source]
-

Says if the format_params is from a hybrid of auto and manual. -Note: Hybrid specifications are considered non-valid and can’t be formatted with format_string.format(…). -Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). -See Also: is_manual_format_params and is_automatic_format_params

-
- -
-
-py2store.key_mappers.str_utils.is_hybrid_format_string(format_string)[source]
-

Says if the format_params is from a hybrid of auto and manual. -Note: Hybrid specifications are considered non-valid and can’t be formatted with format_string.format(…). -Yet, it can be useful for flexibility of expression (but will need to be resolved to be used).

-
>>> is_hybrid_format_string('Manual: indices: {1} {2}, named: {named} {fields}')
-False
->>> is_hybrid_format_string('Auto: only un-indexed and un-named: {} {}...')
-False
->>> is_hybrid_format_string('Hybrid: at least a {}, and a {0} or a {name}')
-True
->>> is_manual_format_string('No formatting is both manual and automatic formatting (so hybrid is both)!')
-True
-
-
-
- -
-
-py2store.key_mappers.str_utils.is_manual_format_params(format_params)[source]
-

Says if the format_params is from a manual specification -See Also: is_automatic_format_params

-
- -
-
-py2store.key_mappers.str_utils.is_manual_format_string(format_string)[source]
-

Says if the format_string uses a manual specification -See Also: is_automatic_format_string and ->>> is_manual_format_string(‘Manual: indices: {1} {2}, named: {named} {fields}’) -True ->>> is_manual_format_string(‘Auto: only un-indexed and un-named: {} {}…’) -False ->>> is_manual_format_string(‘Hybrid: at least a {}, and a {0} or a {name}’) -False ->>> is_manual_format_string(‘No formatting is both manual and automatic formatting!’) -True

-
- -
-
-py2store.key_mappers.str_utils.manual_field_format_str(format_str)[source]
-

Get an auto field version of the format_str

-
-
Parameters
-

format_str – A format string

-
-
Returns
-

A transformed format_str that has no names {inside} {formatting} {braces}.

-
-
-
>>> auto_field_format_str('R/{0}/{one}/{}/{two}/T')
-'R/{}/{}/{}/{}/T'
-
-
-
- -
-
-py2store.key_mappers.str_utils.n_format_params_in_str_format(format_string)[source]
-

The number of parameters

-
- -
-
-py2store.key_mappers.str_utils.name_fields_in_format_str(format_str, field_names=None)[source]
-

Get a manual field version of the format_str

-
-
Parameters
-
    -
  • format_str – A format string

  • -
  • names – An iterable that produces enough strings to fill all of format_str fields

  • -
-
-
Returns
-

A transformed format_str

-
-
-
>>> name_fields_in_format_str('R/{0}/{one}/{}/{two}/T')
-'R/{0}/{1}/{2}/{3}/T'
->>> # Note here that we use the field name to inject a field format as well
->>> name_fields_in_format_str('R/{foo}/{0}/{}/T', ['42', 'hi:03.0f', 'world'])
-'R/{42}/{hi:03.0f}/{world}/T'
-
-
-
- -
-
-

py2store.key_mappers.tuples

-

Tools to map tuple-structured keys. -That is, converting from any of the following kinds of keys:

-
-
    -
  • tuples (or list-like)

  • -
  • dicts

  • -
  • formatted/templated strings

  • -
  • dsv (Delimiter-Separated Values)

  • -
-
-
-
-py2store.key_mappers.tuples.dsv_of_list(d, sep=',')[source]
-

Converting a list of strings to a dsv (delimiter-separated values) string.

-

Note that unlike most key mappers, there is no schema imposing size here. If you wish to impose a size -validation, do so externally (we suggest using a decorator for that).

-
-
Parameters
-
    -
  • d – A list of component strings

  • -
  • sep – The delimiter text used to separate a string into a list of component strings

  • -
-
-
Returns
-

The delimiter-separated values (dsv) string for the input tuple

-
-
-
>>> dsv_of_list(['a', 'brown', 'fox'], sep=' ')
-'a brown fox'
->>> dsv_of_list(('jumps', 'over'), sep='/')  # for filepaths (and see that tuple inputs work too!)
-'jumps/over'
->>> dsv_of_list(['Sat', 'Jan', '1', '1983'], sep=',')  # csv: the usual delimiter-separated values format
-'Sat,Jan,1,1983'
->>> dsv_of_list(['First', 'Last'], sep=':::')  # a longer delimiter
-'First:::Last'
->>> dsv_of_list(['singleton'], sep='@')  # when the list has only one element
-'singleton'
->>> dsv_of_list([], sep='@')  # when the list is empty
-''
-
-
-
- -
-
-py2store.key_mappers.tuples.list_of_dsv(d, sep=',')[source]
-

Converting a dsv (delimiter-separated values) string to the list of it’s components.

-
-
Parameters
-
    -
  • d – A (delimiter-separated values) string

  • -
  • sep – The delimiter text used to separate the string into a list of component strings

  • -
-
-
Returns
-

A list of component strings corresponding to the input delimiter-separated values (dsv) string

-
-
-
>>> list_of_dsv('a brown fox', sep=' ')
-['a', 'brown', 'fox']
->>> tuple(list_of_dsv('jumps/over', sep='/'))  # for filepaths
-('jumps', 'over')
->>> list_of_dsv('Sat,Jan,1,1983', sep=',')  # csv: the usual delimiter-separated values format
-['Sat', 'Jan', '1', '1983']
->>> list_of_dsv('First:::Last', sep=':::')  # a longer delimiter
-['First', 'Last']
->>> list_of_dsv('singleton', sep='@')  # when the list has only one element
-['singleton']
->>> list_of_dsv('', sep='@')  # when the string is empty
-[]
-
-
-
- -
-
-py2store.key_mappers.tuples.mk_obj_of_str(constructor)[source]
-

Make a function that transforms a string to an object. The factory making inverses of what mk_str_from_obj makes.

-
-
Parameters
-

constructor – The function (or class) that will be used to make objects from the **kwargs parsed out of the -string.

-
-
Returns
-

A function factory.

-
-
-
- -
-
-py2store.key_mappers.tuples.mk_str_of_obj(attrs)[source]
-

Make a function that transforms objects to strings, using specific attributes of object.

-
-
Parameters
-

attrs – Attributes that should be read off of the object to make the parameters of the string

-
-
Returns
-

A transformation function

-
-
-
>>> from dataclasses import dataclass
->>> @dataclass
-... class A:
-...     foo: int
-...     bar: str
->>> a = A(foo=0, bar='rin')
->>> a
-A(foo=0, bar='rin')
->>>
->>> str_from_obj = mk_str_of_obj(['foo', 'bar'])
->>> str_from_obj(a, 'ST{foo}/{bar}/G')
-'ST0/rin/G'
-
-
-
- -
-
-py2store.key_mappers.tuples.str_of_tuple(d, str_format)[source]
-

Convert tuple to str. -It’s just str_format.format(*d). Why even write such a function? -(1) To have a consistent interface for key conversions -(2) We want a KeyValidationError to occur here -:param d: tuple if params to str_format -:param str_format: Auto fields format string. If you have manual fields, consider auto_field_format_str to convert.

-
-
Returns
-

parametrized string

-
-
-
>>> str_of_tuple(('hello', 'world'), "Well, {} dear {}!")
-'Well, hello dear world!'
-
-
-
- -
-
-

py2store.key_mappers.paths

-

Module that forwards to py2store.paths, kept for back-compatibility

-
-
-

py2store.key_mappers.naming

-

This module only forwards to py2store.naming, and is deprecated.

-
-
-

py2store.key_mappers

-

key mapping

-
-
-

py2store.errors

-

Forwards to dol.errors:

-

Error objects and utils

-
-
-

py2store.slib.s_configparser

-

Data Object Layer for configparser standard lib.

-
-
-

py2store.slib

-

modules for standard libs

-
-
-

py2store.slib.s_zipfile

-

a data object layer for zipfile

-
-
-exception py2store.slib.s_zipfile.EmptyZipError[source]
-
- -
-
-class py2store.slib.s_zipfile.FileStreamsOfZip(zip_file, prefix='', open_kws=None)[source]
-

Like FilesOfZip, but object returns are file streams instead. -So you use it like this:

-

``` -z = FileStreamsOfZip(rootdir) -with z[relpath] as fp:

-
-

… # do stuff with fp, like fp.readlines() or such…

-
-

```

-
- -
-
-class py2store.slib.s_zipfile.FilesOfZip(zip_file, prefix='', open_kws=None)[source]
-
- -
-
-class py2store.slib.s_zipfile.FlatZipFilesReader(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, zip_reader=<class 'py2store.slib.s_zipfile.ZipReader'>, **zip_reader_kwargs)[source]
-

Read the union of the contents of multiple zip files. -A local file reader whose keys are the zip filepaths of the rootdir and values are corresponding ZipReaders.

-
- -
-
-exception py2store.slib.s_zipfile.OverwriteNotAllowed[source]
-
- -
-
-py2store.slib.s_zipfile.ZipFileReader
-

alias of py2store.slib.s_zipfile.ZipFilesReader

-
- -
-
-class py2store.slib.s_zipfile.ZipFileStreamsReader(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, *, zip_reader=<class 'py2store.slib.s_zipfile.FileStreamsOfZip'>, **zip_reader_kwargs)
-

Like ZipFilesReader, but objects returned are file streams instead.

-
- -
-
-class py2store.slib.s_zipfile.ZipFilesReader(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, zip_reader=<class 'py2store.slib.s_zipfile.ZipReader'>, **zip_reader_kwargs)[source]
-

A local file reader whose keys are the zip filepaths of the rootdir and values are corresponding ZipReaders.

-
- -
-
-class py2store.slib.s_zipfile.ZipFilesReaderAndBytesWriter(rootdir, subpath='.+\\.zip', pattern_for_field=None, max_levels=0, zip_reader=<class 'py2store.slib.s_zipfile.ZipReader'>, **zip_reader_kwargs)[source]
-

Like ZipFilesReader, but the ability to write bytes (assumed to be valid bytes of the zip format) to a key

-
- -
-
-class py2store.slib.s_zipfile.ZipReader(zip_file, prefix='', open_kws=None, file_info_filt=None)[source]
-

A KvReader to read the contents of a zip file. -Provides a KV perspective of https://docs.python.org/3/library/zipfile.html

-

ZipReader has two value categories: Directories and Files. -Both categories are distinguishable by the keys, through the “ends with slash” convention.

-

When a file, the value return is bytes, as usual.

-
-
When a directory, the value returned is a ZipReader itself, with all params the same, except for the prefix

which serves to specify the subfolder (that is, ``prefix` acts as a filter).

-
-
-

Note: If you get data zipped by a mac, you might get some junk along with it. -Namely __MACOSX folders .DS_Store files. I won’t rant about it, since others have. -But you might find it useful to remove them from view. One choice is to use py2store.trans.filt_iter -to get a filtered view of the zips contents. In most cases, this should do the job: -` -# applied to store instance or class: -store = filt_iter(filt=lambda x: not x.startswith('__MACOSX') and '.DS_Store' not in x)(store) -`

-

Another option is just to remove these from the zip file once and for all. In unix-like systems: -` -zip -d filename.zip __MACOSX/\* -zip -d filename.zip \*/.DS_Store -`

-

Examples

-

# >>> s = ZipReader(‘/path/to/some_zip_file.zip’) -# >>> len(s) -# 53432 -# >>> list(s)[:3] # the first 3 elements (well… their keys) -# [‘odir/’, ‘odir/app/’, ‘odir/app/data/’] -# >>> list(s)[-3:] # the last 3 elements (well… their keys) -# [‘odir/app/data/audio/d/1574287049078391/m/Ctor.json’, -# ‘odir/app/data/audio/d/1574287049078391/m/intensity.json’, -# ‘odir/app/data/run/status.json’] -# >>> # getting a file (note that by default, you get bytes, so need to decode) -# >>> s[‘odir/app/data/run/status.json’].decode() -# b’{“test_phase_number”: 9, “test_phase”: “TestActions.IGNORE_TEST”, “session_id”: 0}’ -# >>> # when you ask for the contents for a key that’s a directory, -# >>> # you get a ZipReader filtered for that prefix: -# >>> s[‘odir/app/data/audio/’] -# ZipReader(‘/path/to/some_zip_file.zip’, ‘odir/app/data/audio/’, {}, <function take_everything at 0x1538999e0>) -# >>> # Often, you only want files (not directories) -# >>> # You can filter directories out using the file_info_filt argument -# >>> s = ZipReader(‘/path/to/some_zip_file.zip’, file_info_filt=ZipReader.FILES_ONLY) -# >>> len(s) # compare to the 53432 above, that contained dirs too -# 53280 -# >>> list(s)[:3] # first 3 keys are all files now -# [‘odir/app/data/plc/d/1574304926795633/d/1574305026895702’, -# ‘odir/app/data/plc/d/1574304926795633/d/1574305276853053’, -# ‘odir/app/data/plc/d/1574304926795633/d/1574305159343326’] -# >>> -# >>> # ZipReader.FILES_ONLY and ZipReader.DIRS_ONLY are just convenience filt functions -# >>> # Really, you can provide any custom one yourself. -# >>> # This filter function should take a ZipInfo object, and return True or False. -# >>> # (https://docs.python.org/3/library/zipfile.html#zipfile.ZipInfo) -# >>> -# >>> import re -# >>> p = re.compile(‘audio.*.json$’) -# >>> my_filt_func = lambda fileinfo: bool(p.search(fileinfo.filename)) -# >>> s = ZipReader(‘/Users/twhalen/Downloads/2019_11_21.zip’, file_info_filt=my_filt_func) -# >>> len(s) -# 48 -# >>> list(s)[:3] -# [‘odir/app/data/audio/d/1574333557263758/m/Ctor.json’, -# ‘odir/app/data/audio/d/1574333557263758/m/intensity.json’, -# ‘odir/app/data/audio/d/1574288084739961/m/Ctor.json’]

-
- -
-
-class py2store.slib.s_zipfile.ZipStore(zip_filepath, compression=8, allow_overwrites=True, pwd=None)[source]
-

Zip read and writing. -When you want to read zips, there’s the FilesOfZip, ZipReader, or ZipFilesReader we know and love.

-

Sometimes though, you want to write to zips too. For this, we have ZipStore.

-

Since ZipStore can write to a zip, it’s read functionality is not going to assume static data, -and cache things, as your favorite zip readers did. -This, and the acrobatics need to disguise the weird zipfile into something more… key-value natural, -makes for a not so efficient store, out of the box.

-
-
I advise using one of the zip readers if all you need to do is read, or subclassing or

wrapping ZipStore with caching layers if it is appropriate to you.

-
-
-
- -
-
-py2store.slib.s_zipfile.func_conjunction(func1, func2)[source]
-

Returns a function that is equivalent to lambda x: func1(x) and func2(x)

-
- -
-
-py2store.slib.s_zipfile.mk_flatzips_store(dir_of_zips, zip_pair_path_preproc=<built-in function sorted>, mk_store=<class 'py2store.slib.s_zipfile.FlatZipFilesReader'>, **extra_mk_store_kwargs)[source]
-

A store so that you can work with a folder that has a bunch of zip files, -as if they’ve all been extracted in the same folder. -Note that zip_pair_path_preproc can be used to control how to resolve key conflicts -(i.e. when you get two different zip files that have a same path in their contents). -The last path encountered by zip_pair_path_preproc(zip_path_pairs) is the one that will be used, so -one should make zip_pair_path_preproc act accordingly.

-
- -
-
-

py2store.base

-

Forwards to dol.base:

-

Base classes for making stores. -In the language of the collections.abc module, a store is a MutableMapping that is configured to work with a specific -representation of keys, serialization of objects (python values), and persistence of the serialized data.

-

That is, stores offer the same interface as a dict, but where the actual implementation of writes, reads, and listing -are configurable.

-

Consider the following example. You’re store is meant to store waveforms as wav files on a remote server. -Say waveforms are represented in python as a tuple (wf, sr), where wf is a list of numbers and sr is the sample -rate, an int). The __setitem__ method will specify how to store bytes on a remote server, but you’ll need to specify -how to SERIALIZE (wf, sr) to the bytes that constitute that wav file: _data_of_obj specifies that. -You might also want to read those wav files back into a python (wf, sr) tuple. The __getitem__ method will get -you those bytes from the server, but the store will need to know how to DESERIALIZE those bytes back into a python -object: _obj_of_data specifies that

-

Further, say you’re storing these .wav files in /some/folder/on/the/server/, but you don’t want the store to use -these as the keys. For one, it’s annoying to type and harder to read. But more importantly, it’s an irrelevant -implementation detail that shouldn’t be exposed. THe _id_of_key and _key_of_id pair are what allow you to -add this key interface layer.

-

These key converters object serialization methods default to the identity (i.e. they return the input as is). -This means that you don’t have to implement these as all, and can choose to implement these concerns within -the storage methods themselves.

-
-
-

py2store.selectors.mg_selectors

-
-
-

py2store.selectors.mongoquery

-
-
-

py2store.selectors

-
-
-

py2store.parse_format

-

Modified from https://github.com/r1chardj0n3s/parse

-

Parse strings using a specification based on the Python format() syntax.

-
-

parse() is the opposite of format()

-
-

From there it’s a simple thing to parse a string:

-
>>> parse("It's {}, I love it!", "It's spam, I love it!")
-<Result ('spam',) {}>
->>> _[0]
-'spam'
-
-
-

Or to search a string for some pattern:

-
>>> search('Age: {:d}\n', 'Name: Rufus\nAge: 42\nColor: red\n')
-<Result (42,) {}>
-
-
-

Or find all the occurrences of some pattern in a string:

-
>>> ''.join(r.fixed[0] for r in findall(">{}<", "<p>the <b>bold</b> text</p>"))
-'the bold text'
-
-
-

If you’re going to use the same pattern to match lots of strings you can -compile it once:

-
>>> p = compile("It's {}, I love it!")
->>> print(p)
-<Parser "It's {}, I love it!">
->>> p.parse("It's spam, I love it!")
-<Result ('spam',) {}>
-
-
-

(“compile” is not exported for import * usage as it would override the -built-in compile() function)

-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True:

-
>>> parse('SPAM', 'spam', case_sensitive=True) is None
-True
-
-
-
-

Format Syntax

-

A basic version of the Format String Syntax is supported with anonymous -(fixed-position), named and formatted fields:

-
{[field name]:[format spec]}
-
-
-

Field names must be a valid Python identifiers, including dotted names; -element indexes imply dictionaries (see below for example).

-

Numbered fields are also not supported: the result of parsing will include -the parsed fields in the order they are parsed.

-

The conversion of fields to types other than strings is done based on the -type in the format specification, which mirrors the format() behaviour. -There are no “!” field conversions like format() has.

-

Some simple parse() format string examples:

-
>>> parse("Bring me a {}", "Bring me a shrubbery")
-<Result ('shrubbery',) {}>
->>> r = parse("The {} who say {}", "The knights who say Ni!")
->>> print(r)
-<Result ('knights', 'Ni!') {}>
->>> print(r.fixed)
-('knights', 'Ni!')
->>> r = parse("Bring out the holy {item}", "Bring out the holy hand grenade")
->>> print(r)
-<Result () {'item': 'hand grenade'}>
->>> print(r.named)
-{'item': 'hand grenade'}
->>> print(r['item'])
-hand grenade
-
-
-

Dotted names and indexes are possible though the application must make -additional sense of the result:

-
>>> r = parse("Mmm, {food.type}, I love it!", "Mmm, spam, I love it!")
->>> print(r)
-<Result () {'food.type': 'spam'}>
->>> print(r.named)
-{'food.type': 'spam'}
->>> print(r['food.type'])
-spam
->>> r = parse("My quest is {quest[name]}", "My quest is to seek the holy grail!")
->>> print(r)
-<Result () {'quest': {'name': 'to seek the holy grail!'}}>
->>> print(r['quest'])
-{'name': 'to seek the holy grail!'}
->>> print(r['quest']['name'])
-to seek the holy grail!
-
-
-

If the text you’re matching has braces in it you can match those by including -a double-brace {{ or }} in your format string, just like format() does.

-
-
-

Format Specification

-

Most often a straight format-less {} will suffice where a more complex -format specification might have been used.

-

Most of format()’s Format Specification Mini-Language is supported:

-
-

[[fill]align][0][width][.precision][type]

-
-

The differences between parse() and format() are:

-
    -
  • The align operators will cause spaces (or specified fill character) to be -stripped from the parsed value. The width is not enforced; it just indicates -there may be whitespace or “0”s to strip.

  • -
  • Numeric parsing will automatically handle a “0b”, “0o” or “0x” prefix. -That is, the “#” format character is handled automatically by d, b, o -and x formats. For “d” any will be accepted, but for the others the correct -prefix must be present if at all.

  • -
  • Numeric sign is handled automatically.

  • -
  • The thousands separator is handled automatically if the “n” type is used.

  • -
  • The types supported are a slightly different mix to the format() types. Some -format() types come directly over: “d”, “n”, “%”, “f”, “e”, “b”, “o” and “x”. -In addition some regular expression character group types “D”, “w”, “W”, “s” -and “S” are also available.

  • -
  • The “e” and “g” types are case-insensitive so there is not need for -the “E” or “G” types.

  • -
- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Type

Characters Matched

Output

w

Letters and underscore

str

W

Non-letter and underscore

str

s

Whitespace

str

S

Non-whitespace

str

d

Digits (effectively integer numbers)

int

D

Non-digit

str

n

Numbers with thousands separators (, or .)

int

%

Percentage (converted to value/100.0)

float

f

Fixed-point numbers

float

F

Decimal numbers

Decimal

e

Floating-point numbers with exponent -e.g. 1.1e-10, NAN (all case insensitive)

float

g

General number format (either d, f or e)

float

b

Binary numbers

int

o

Octal numbers

int

x

Hexadecimal numbers (lower and upper case)

int

ti

ISO 8601 format date/time -e.g. 1972-01-20T10:21:36Z (“T” and “Z” -optional)

datetime

te

RFC2822 e-mail format date/time -e.g. Mon, 20 Jan 1972 10:21:36 +1000

datetime

tg

Global (day/month) format date/time -e.g. 20/1/1972 10:21:36 AM +1:00

datetime

ta

US (month/day) format date/time -e.g. 1/20/1972 10:21:36 PM +10:30

datetime

tc

ctime() format date/time -e.g. Sun Sep 16 01:03:52 1973

datetime

th

HTTP log format date/time -e.g. 21/Nov/2011:00:07:11 +0000

datetime

ts

Linux system log format date/time -e.g. Nov 9 03:37:44

datetime

tt

Time -e.g. 10:21:36 PM -5:30

time

-

Some examples of typed parsing with None returned if the typing -does not match:

-
>>> parse('Our {:d} {:w} are...', 'Our 3 weapons are...')
-<Result (3, 'weapons') {}>
->>> parse('Our {:d} {:w} are...', 'Our three weapons are...')
->>> parse('Meet at {:tg}', 'Meet at 1/2/2011 11:00 PM')
-<Result (datetime.datetime(2011, 2, 1, 23, 0),) {}>
-
-
-

And messing about with alignment:

-
>>> parse('with {:>} herring', 'with     a herring')
-<Result ('a',) {}>
->>> parse('spam {:^} spam', 'spam    lovely     spam')
-<Result ('lovely',) {}>
-
-
-

Note that the “center” alignment does not test to make sure the value is -centered - it just strips leading and trailing whitespace.

-

Width and precision may be used to restrict the size of matched text -from the input. Width specifies a minimum size and precision specifies -a maximum. For example:

-
>>> parse('{:.2}{:.2}', 'look')           # specifying precision
-<Result ('lo', 'ok') {}>
->>> parse('{:4}{:4}', 'look at that')     # specifying width
-<Result ('look', 'at that') {}>
->>> parse('{:4}{:.4}', 'look at that')    # specifying both
-<Result ('look at ', 'that') {}>
->>> parse('{:2d}{:2d}', '0440')           # parsing two contiguous numbers
-<Result (4, 40) {}>
-
-
-

Some notes for the date and time types:

-
    -
  • the presence of the time part is optional (including ISO 8601, starting -at the “T”). A full datetime object will always be returned; the time -will be set to 00:00:00. You may also specify a time without seconds.

  • -
  • when a seconds amount is present in the input fractions will be parsed -to give microseconds.

  • -
  • except in ISO 8601 the day and month digits may be 0-padded.

  • -
  • the date separator for the tg and ta formats may be “-” or “/”.

  • -
  • named months (abbreviations or full names) may be used in the ta and tg -formats in place of numeric months.

  • -
  • as per RFC 2822 the e-mail format may omit the day (and comma), and the -seconds but nothing else.

  • -
  • hours greater than 12 will be happily accepted.

  • -
  • the AM/PM are optional, and if PM is found then 12 hours will be added -to the datetime object’s hours amount - even if the hour is greater -than 12 (for consistency.)

  • -
  • in ISO 8601 the “Z” (UTC) timezone part may be a numeric offset

  • -
  • timezones are specified as “+HH:MM” or “-HH:MM”. The hour may be one or two -digits (0-padded is OK.) Also, the “:” is optional.

  • -
  • the timezone is optional in all except the e-mail format (it defaults to -UTC.)

  • -
  • named timezones are not handled yet.

  • -
-

Note: attempting to match too many datetime fields in a single parse() will -currently result in a resource allocation issue. A TooManyFields exception -will be raised in this instance. The current limit is about 15. It is hoped -that this limit will be removed one day.

-
-
-

Result and Match Objects

-

The result of a parse() and search() operation is either None (no match), a -Result instance or a Match instance if evaluate_result is False.

-

The Result instance has three attributes:

-
-
fixed

A tuple of the fixed-position, anonymous fields extracted from the input.

-
-
named

A dictionary of the named fields extracted from the input.

-
-
spans

A dictionary mapping the names and fixed position indices matched to a -2-tuple slice range of where the match occurred in the input. -The span does not include any stripped padding (alignment or width).

-
-
-

The Match instance has one method:

-
-
evaluate_result()

Generates and returns a Result instance for this Match object.

-
-
-
-
-

Custom Type Conversions

-

If you wish to have matched fields automatically converted to your own type you -may pass in a dictionary of type conversion information to parse() and -compile().

-

The converter will be passed the field string matched. Whatever it returns -will be substituted in the Result instance for that field.

-

Your custom type conversions may override the builtin types if you supply one -with the same identifier.

-
>>> def shouty(string):
-...    return string.upper()
-...
->>> parse('{:shouty} world', 'hello world', dict(shouty=shouty))
-<Result ('HELLO',) {}>
-
-
-

If the type converter has the optional pattern attribute, it is used as -regular expression for better pattern matching (instead of the default one).

-
>>> def parse_number(text):
-...    return int(text)
->>> parse_number.pattern = r'\d+'
->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
-<Result () {'number': 42}>
->>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number))
->>> assert _ is None, "MISMATCH"
-
-
-

You can also use the with_pattern(pattern) decorator to add this -information to a type converter function:

-
>>> @with_pattern(r'\d+')
-... def parse_number(text):
-...    return int(text)
->>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
-<Result () {'number': 42}>
-
-
-

A more complete example of a custom type might be:

-
>>> yesno_mapping = {
-...     "yes":  True,   "no":    False,
-...     "on":   True,   "off":   False,
-...     "true": True,   "false": False,
-... }
->>> @with_pattern(r"|".join(yesno_mapping))
-... def parse_yesno(text):
-...     return yesno_mapping[text.lower()]
-
-
-

If the type converter pattern uses regex-grouping (with parenthesis), -you should indicate this by using the optional regex_group_count parameter -in the with_pattern() decorator:

-
>>> @with_pattern(r'((\d+))', regex_group_count=2)
-... def parse_number2(text):
-...    return int(text)
->>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
-<Result (42, 43) {}>
-
-
-

Otherwise, this may cause parsing problems with unnamed/fixed parameters.

-
-
-

Potential Gotchas

-

parse() will always match the shortest text necessary (from left to right) -to fulfil the parse pattern, so for example:

-
>>> pattern = '{dir1}/{dir2}'
->>> data = 'root/parent/subdir'
->>> sorted(parse(pattern, data).named.items())
-[('dir1', 'root'), ('dir2', 'parent/subdir')]
-
-
-

So, even though {‘dir1’: ‘root/parent’, ‘dir2’: ‘subdir’} would also fit -the pattern, the actual match represents the shortest successful match for -dir1.

-
-

Version history (in brief):

-
    -
  • 1.9.0 We now honor precision and width specifiers when parsing numbers -and strings, allowing parsing of concatenated elements of fixed width -(thanks Julia Signell)

  • -
  • 1.8.4 Add LICENSE file at request of packagers. -Correct handling of AM/PM to follow most common interpretation. -Correct parsing of hexadecimal that looks like a binary prefix. -Add ability to parse case sensitively. -Add parsing of numbers to Decimal with “F” (thanks John Vandenberg)

  • -
  • 1.8.3 Add regex_group_count to with_pattern() decorator to support -user-defined types that contain brackets/parenthesis (thanks Jens Engel)

  • -
  • 1.8.2 add documentation for including braces in format string

  • -
  • 1.8.1 ensure bare hexadecimal digits are not matched

  • -
  • 1.8.0 support manual control over result evaluation (thanks Timo Furrer)

  • -
  • 1.7.0 parse dict fields (thanks Mark Visser) and adapted to allow -more than 100 re groups in Python 3.5+ (thanks David King)

  • -
  • 1.6.6 parse Linux system log dates (thanks Alex Cowan)

  • -
  • 1.6.5 handle precision in float format (thanks Levi Kilcher)

  • -
  • 1.6.4 handle pipe “|” characters in parse string (thanks Martijn Pieters)

  • -
  • 1.6.3 handle repeated instances of named fields, fix bug in PM time -overflow

  • -
  • 1.6.2 fix logging to use local, not root logger (thanks Necku)

  • -
  • 1.6.1 be more flexible regarding matched ISO datetimes and timezones in -general, fix bug in timezones without “:” and improve docs

  • -
  • 1.6.0 add support for optional pattern attribute in user-defined types -(thanks Jens Engel)

  • -
  • 1.5.3 fix handling of question marks

  • -
  • 1.5.2 fix type conversion error with dotted names (thanks Sebastian Thiel)

  • -
  • 1.5.1 implement handling of named datetime fields

  • -
  • 1.5 add handling of dotted field names (thanks Sebastian Thiel)

  • -
  • 1.4.1 fix parsing of “0” in int conversion (thanks James Rowe)

  • -
  • 1.4 add __getitem__ convenience access on Result.

  • -
  • 1.3.3 fix Python 2.5 setup.py issue.

  • -
  • 1.3.2 fix Python 3.2 setup.py issue.

  • -
  • 1.3.1 fix a couple of Python 3.2 compatibility issues.

  • -
  • 1.3 added search() and findall(); removed compile() from import * -export as it overwrites builtin.

  • -
  • 1.2 added ability for custom and override type conversions to be -provided; some cleanup

  • -
  • 1.1.9 to keep things simpler number sign is handled automatically; -significant robustification in the face of edge-case input.

  • -
  • 1.1.8 allow “d” fields to have number base “0x” etc. prefixes; -fix up some field type interactions after stress-testing the parser; -implement “%” type.

  • -
  • 1.1.7 Python 3 compatibility tweaks (2.5 to 2.7 and 3.2 are supported).

  • -
  • 1.1.6 add “e” and “g” field types; removed redundant “h” and “X”; -removed need for explicit “#”.

  • -
  • 1.1.5 accept textual dates in more places; Result now holds match span -positions.

  • -
  • 1.1.4 fixes to some int type conversion; implemented “=” alignment; added -date/time parsing with a variety of formats handled.

  • -
  • 1.1.3 type conversion is automatic based on specified field types. Also added -“f” and “n” types.

  • -
  • 1.1.2 refactored, added compile() and limited from parse import *

  • -
  • 1.1.1 documentation improvements

  • -
  • 1.1.0 implemented more of the Format Specification Mini-Language -and removed the restriction on mixing fixed-position and named fields

  • -
  • 1.0.0 initial release

  • -
-

This code is copyright 2012-2017 Richard Jones <richard@python.org> -See the end of the source file for the license of use.

-
-
-py2store.parse_format.findall(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, case_sensitive=False)[source]
-

Search “string” for all occurrences of “format”.

-

You will be returned an iterator that holds Result instances -for each format match found.

-

Optionally start the search at “pos” character index and limit the search -to a maximum index of endpos - equivalent to search(string[:endpos]).

-

If evaluate_result is True each returned Result instance has two attributes:

-
-

.fixed - tuple of fixed-position values from the string -.named - dict of named values from the string

-
-

If evaluate_result is False each returned value is a Match instance with one method:

-
-
-
.evaluate_result() - This will return a Result instance like you would get

with evaluate_result set to True

-
-
-
-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True.

-

If the format is invalid a ValueError will be raised.

-

See the module documentation for the use of “extra_types”.

-
- -
-
-py2store.parse_format.parse(format, string, extra_types=None, evaluate_result=True, case_sensitive=False)[source]
-

Using “format” attempt to pull values from “string”.

-

The format must match the string contents exactly. If the value -you’re looking for is instead just a part of the string use -search().

-

If evaluate_result is True the return value will be an Result instance with two attributes:

-
-

.fixed - tuple of fixed-position values from the string -.named - dict of named values from the string

-
-

If evaluate_result is False the return value will be a Match instance with one method:

-
-
-
.evaluate_result() - This will return a Result instance like you would get

with evaluate_result set to True

-
-
-
-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True.

-

If the format is invalid a ValueError will be raised.

-

See the module documentation for the use of “extra_types”.

-

In the case there is no match parse() will return None.

-
- -
-
-py2store.parse_format.search(format, string, pos=0, endpos=None, extra_types=None, evaluate_result=True, case_sensitive=False)[source]
-

Search “string” for the first occurrence of “format”.

-

The format may occur anywhere within the string. If -instead you wish for the format to exactly match the string -use parse().

-

Optionally start the search at “pos” character index and limit the search -to a maximum index of endpos - equivalent to search(string[:endpos]).

-

If evaluate_result is True the return value will be an Result instance with two attributes:

-
-

.fixed - tuple of fixed-position values from the string -.named - dict of named values from the string

-
-

If evaluate_result is False the return value will be a Match instance with one method:

-
-
-
.evaluate_result() - This will return a Result instance like you would get

with evaluate_result set to True

-
-
-
-

The default behaviour is to match strings case insensitively. You may match with -case by specifying case_sensitive=True.

-

If the format is invalid a ValueError will be raised.

-

See the module documentation for the use of “extra_types”.

-

In the case there is no match parse() will return None.

-
- -
-
-py2store.parse_format.with_pattern(pattern, regex_group_count=None)[source]
-

Attach a regular expression pattern matcher to a custom type converter -function.

-

This annotates the type converter with the pattern attribute.

-

Example

-
>>> @with_pattern(r"\d+")
-... def parse_number(text):
-...     return int(text)
-
-
-

is equivalent to:

-
>>> def parse_number(text):
-...     return int(text)
->>> parse_number.pattern = r"\d+"
-
-
-
-
Parameters
-
    -
  • pattern – regular expression pattern (as text)

  • -
  • regex_group_count – Indicates how many regex-groups are in pattern.

  • -
-
-
Returns
-

wrapped function

-
-
-
- -
-
- - -
- -
-
- -
-
- - - - - - - \ No newline at end of file diff --git a/docsrc/.gitignore b/docsrc/.gitignore deleted file mode 100644 index 69fa449..0000000 --- a/docsrc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_build/ diff --git a/docsrc/Makefile b/docsrc/Makefile deleted file mode 100644 index 5f01b86..0000000 --- a/docsrc/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build -GITHUBPAGESDIR = ../docs -GITLABPAGESDIR = ../public - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -github: - @make html - @cp -a "$(BUILDDIR)"/html/. $(GITHUBPAGESDIR) - -gitlab: - @make html - @cp -a "$(BUILDDIR)"/html/. $(GITLABPAGESDIR) - -clean: - rm -rfv $(BUILDDIR) $(GITHUBPAGESDIR) $(GITLABPAGESDIR) - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docsrc/conf.py b/docsrc/conf.py deleted file mode 100644 index 1893aec..0000000 --- a/docsrc/conf.py +++ /dev/null @@ -1,60 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. -# For a full list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -import os -import sys - -sys.path.insert(0, os.path.abspath('..')) - -# -- Project information ----------------------------------------------------- -from epythet.config_parser import parse_config -from pathlib import Path - -project, copyright, author, release, display_name = parse_config( - Path(__file__).absolute().parent.parent / 'setup.cfg' -) - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', # Include documentation from docstrings - 'sphinx.ext.doctest', # Test snippets in the documentation - 'sphinx.ext.githubpages', # This extension creates .nojekyll file - 'sphinx.ext.graphviz', # Add Graphviz graphs - 'sphinx.ext.napoleon', # Support for NumPy and Google style docstrings - 'sphinx.ext.todo', # Support for todo items - 'sphinx.ext.viewcode', # Add links to highlighted source code - 'recommonmark', # Parse .md files -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] diff --git a/docsrc/config_store_test.ini b/docsrc/config_store_test.ini deleted file mode 100644 index 79a29af..0000000 --- a/docsrc/config_store_test.ini +++ /dev/null @@ -1,7 +0,0 @@ -[nothing] -special = about -number = 42 - -[add] -more = sections - diff --git a/docsrc/how_to.md b/docsrc/how_to.md deleted file mode 100644 index 5574e2f..0000000 --- a/docsrc/how_to.md +++ /dev/null @@ -1,79 +0,0 @@ - -# A reader of multiple zip files - -You'll find the main zip file handling stuff in module: `py2store.slib.s_zipfile`. - - -Let's assume the most common case where your zips are in a folder (and possibly subfolders). -The three following objects should cover most of your cases: - -```python -from py2store.slib.s_zipfile import ZipFilesReader, FlatZipFilesReader, mk_flatzips_store -``` - -That's in order of dependency ( `mk_flatzips_store` uses `FlatZipFilesReader` uses `ZipFilesReader`). - - -## ZipFilesReader - -Gives you a reader whose keys are full paths to zip files and values are `ZipReader` objects (by default, but customizable). - -```python -z = ZipFilesReader(zipdir) -k, v = z.head() # get a key and a value to see what this store is about -print(f"{k=}") -print(f"{type(v)=} {len(v)=}") -``` - -``` -k='/Users/data/zips/some_zip_file.zip' -type(v)= len(v)=45 -``` - -The length of `v` is the number of files in the `some_zip_file.zip` zip file. - - -## FlatZipFilesReader - -With `ZipFilesReader` you get access to zips of a folder, and contents of the zips through the values it provides. -But sometimes you want to have direct access to the contents of multiple zips. -`FlatZipFilesReader` will provide you with that. - -It's keys are `(relative_zip_filepath, key_within_that_zip_file)` pairs and it's values are bytes of the zip content the key is pointing to. - -```python -z = FlatZipFilesReader(zipdir) -k, v = z.head() # get a key and a value to see what this store is about -print(f"{k=}") -print(f"{type(v)=} {len(v)=}") -``` - -``` -k=('some_zip_file.zip', 'some_folder_in_zip/a_subfolder/a_file.xlsx') -type(v)= len(v)=19430710 -``` - -The length here is the number of bytes that `a_file.xlsx` has. - -## mk_flatzips_store - -Sometimes using `(relative_zip_filepath, key_within_that_zip_file)` as keys is not practical. -When you don't like the key language, you can always change it. -You have `trans.wrap_kvs` to help with that, as well as several specialized tools like `key_mappers.naming`, etc. - -In our current case, one practical key to use would be to just use the second element of the pair: The key of the particular zip file. -This isn't a problem as long as they are all unique (amongst the multiple zip files). -The `mk_flatzips_store` does that work for you -- checking for unicity and giving you a reader that uses such simpler keys: - -```python -z = mk_flatzips_store(zipdir) -k, v = z.head() # get a key and a value to see what this store is about -print(f"{k=}") -print(f"{type(v)=} {len(v)=}") -``` - -``` -k='some_folder_in_zip/a_subfolder/a_file.xlsx' -type(v)= len(v)=1893 -``` - diff --git a/docsrc/http_docs.rst b/docsrc/http_docs.rst deleted file mode 100644 index b1a8444..0000000 --- a/docsrc/http_docs.rst +++ /dev/null @@ -1 +0,0 @@ -.. automodule:: mockmodule.http_docs diff --git a/docsrc/index.rst b/docsrc/index.rst deleted file mode 100644 index 6d94e72..0000000 --- a/docsrc/index.rst +++ /dev/null @@ -1,13 +0,0 @@ -Welcome to py2store's documentation! -==================================== - - -.. include:: ./table_of_contents.rst - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docsrc/make.bat b/docsrc/make.bat deleted file mode 100644 index 2119f51..0000000 --- a/docsrc/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docsrc/mockobjects.rst b/docsrc/mockobjects.rst deleted file mode 100644 index f710a7f..0000000 --- a/docsrc/mockobjects.rst +++ /dev/null @@ -1,5 +0,0 @@ -Mock Objects -============ - -.. automodule:: mockmodule.mockobjects - :members: diff --git a/docsrc/module_docs/py2store.rst b/docsrc/module_docs/py2store.rst deleted file mode 100644 index 8801d26..0000000 --- a/docsrc/module_docs/py2store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store -======== -.. automodule:: py2store - :members: diff --git a/docsrc/module_docs/py2store/access.rst b/docsrc/module_docs/py2store/access.rst deleted file mode 100644 index ab53ffa..0000000 --- a/docsrc/module_docs/py2store/access.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.access -=============== -.. automodule:: py2store.access - :members: diff --git a/docsrc/module_docs/py2store/appendable.rst b/docsrc/module_docs/py2store/appendable.rst deleted file mode 100644 index 450c7eb..0000000 --- a/docsrc/module_docs/py2store/appendable.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.appendable -=================== -.. automodule:: py2store.appendable - :members: diff --git a/docsrc/module_docs/py2store/base.rst b/docsrc/module_docs/py2store/base.rst deleted file mode 100644 index a3a5892..0000000 --- a/docsrc/module_docs/py2store/base.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.base -============= -.. automodule:: py2store.base - :members: diff --git a/docsrc/module_docs/py2store/caching.rst b/docsrc/module_docs/py2store/caching.rst deleted file mode 100644 index b455a58..0000000 --- a/docsrc/module_docs/py2store/caching.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.caching -================ -.. automodule:: py2store.caching - :members: diff --git a/docsrc/module_docs/py2store/core.rst b/docsrc/module_docs/py2store/core.rst deleted file mode 100644 index 9a54cb1..0000000 --- a/docsrc/module_docs/py2store/core.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.core -============= -.. automodule:: py2store.core - :members: diff --git a/docsrc/module_docs/py2store/dig.rst b/docsrc/module_docs/py2store/dig.rst deleted file mode 100644 index d084d5c..0000000 --- a/docsrc/module_docs/py2store/dig.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.dig -============ -.. automodule:: py2store.dig - :members: diff --git a/docsrc/module_docs/py2store/errors.rst b/docsrc/module_docs/py2store/errors.rst deleted file mode 100644 index 917b152..0000000 --- a/docsrc/module_docs/py2store/errors.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.errors -=============== -.. automodule:: py2store.errors - :members: diff --git a/docsrc/module_docs/py2store/examples.rst b/docsrc/module_docs/py2store/examples.rst deleted file mode 100644 index 1af20ef..0000000 --- a/docsrc/module_docs/py2store/examples.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples -================= -.. automodule:: py2store.examples - :members: diff --git a/docsrc/module_docs/py2store/examples/code_navig.rst b/docsrc/module_docs/py2store/examples/code_navig.rst deleted file mode 100644 index 53797e8..0000000 --- a/docsrc/module_docs/py2store/examples/code_navig.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.code_navig -============================ -.. automodule:: py2store.examples.code_navig - :members: diff --git a/docsrc/module_docs/py2store/examples/dropbox_w_urllib.rst b/docsrc/module_docs/py2store/examples/dropbox_w_urllib.rst deleted file mode 100644 index 835398b..0000000 --- a/docsrc/module_docs/py2store/examples/dropbox_w_urllib.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.dropbox_w_urllib -================================== -.. automodule:: py2store.examples.dropbox_w_urllib - :members: diff --git a/docsrc/module_docs/py2store/examples/kv_walking.rst b/docsrc/module_docs/py2store/examples/kv_walking.rst deleted file mode 100644 index abdc655..0000000 --- a/docsrc/module_docs/py2store/examples/kv_walking.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.kv_walking -============================ -.. automodule:: py2store.examples.kv_walking - :members: diff --git a/docsrc/module_docs/py2store/examples/last_key_inserted.rst b/docsrc/module_docs/py2store/examples/last_key_inserted.rst deleted file mode 100644 index d1fa8d7..0000000 --- a/docsrc/module_docs/py2store/examples/last_key_inserted.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.last_key_inserted -=================================== -.. automodule:: py2store.examples.last_key_inserted - :members: diff --git a/docsrc/module_docs/py2store/examples/python_code_stats.rst b/docsrc/module_docs/py2store/examples/python_code_stats.rst deleted file mode 100644 index 6e362f7..0000000 --- a/docsrc/module_docs/py2store/examples/python_code_stats.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.python_code_stats -=================================== -.. automodule:: py2store.examples.python_code_stats - :members: diff --git a/docsrc/module_docs/py2store/examples/write_caches.rst b/docsrc/module_docs/py2store/examples/write_caches.rst deleted file mode 100644 index b149e11..0000000 --- a/docsrc/module_docs/py2store/examples/write_caches.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.examples.write_caches -============================== -.. automodule:: py2store.examples.write_caches - :members: diff --git a/docsrc/module_docs/py2store/ext.rst b/docsrc/module_docs/py2store/ext.rst deleted file mode 100644 index 0e2ae09..0000000 --- a/docsrc/module_docs/py2store/ext.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext -============ -.. automodule:: py2store.ext - :members: diff --git a/docsrc/module_docs/py2store/ext/audio.rst b/docsrc/module_docs/py2store/ext/audio.rst deleted file mode 100644 index a4c8a4e..0000000 --- a/docsrc/module_docs/py2store/ext/audio.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.audio -================== -.. automodule:: py2store.ext.audio - :members: diff --git a/docsrc/module_docs/py2store/ext/dataframes.rst b/docsrc/module_docs/py2store/ext/dataframes.rst deleted file mode 100644 index 5e1f2a7..0000000 --- a/docsrc/module_docs/py2store/ext/dataframes.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.dataframes -======================= -.. automodule:: py2store.ext.dataframes - :members: diff --git a/docsrc/module_docs/py2store/ext/docx.rst b/docsrc/module_docs/py2store/ext/docx.rst deleted file mode 100644 index 4ae6a5d..0000000 --- a/docsrc/module_docs/py2store/ext/docx.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.docx -================= -.. automodule:: py2store.ext.docx - :members: diff --git a/docsrc/module_docs/py2store/ext/github.rst b/docsrc/module_docs/py2store/ext/github.rst deleted file mode 100644 index a0b976f..0000000 --- a/docsrc/module_docs/py2store/ext/github.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.github -=================== -.. automodule:: py2store.ext.github - :members: diff --git a/docsrc/module_docs/py2store/ext/gitlab.rst b/docsrc/module_docs/py2store/ext/gitlab.rst deleted file mode 100644 index a8cd8d1..0000000 --- a/docsrc/module_docs/py2store/ext/gitlab.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.gitlab -=================== -.. automodule:: py2store.ext.gitlab - :members: diff --git a/docsrc/module_docs/py2store/ext/hdf.rst b/docsrc/module_docs/py2store/ext/hdf.rst deleted file mode 100644 index 3e58a93..0000000 --- a/docsrc/module_docs/py2store/ext/hdf.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.hdf -================ -.. automodule:: py2store.ext.hdf - :members: diff --git a/docsrc/module_docs/py2store/ext/kaggle.rst b/docsrc/module_docs/py2store/ext/kaggle.rst deleted file mode 100644 index 756379b..0000000 --- a/docsrc/module_docs/py2store/ext/kaggle.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.kaggle -=================== -.. automodule:: py2store.ext.kaggle - :members: diff --git a/docsrc/module_docs/py2store/ext/matlab.rst b/docsrc/module_docs/py2store/ext/matlab.rst deleted file mode 100644 index 5610eb4..0000000 --- a/docsrc/module_docs/py2store/ext/matlab.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.matlab -=================== -.. automodule:: py2store.ext.matlab - :members: diff --git a/docsrc/module_docs/py2store/ext/module_imports.rst b/docsrc/module_docs/py2store/ext/module_imports.rst deleted file mode 100644 index 61981f2..0000000 --- a/docsrc/module_docs/py2store/ext/module_imports.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.module_imports -=========================== -.. automodule:: py2store.ext.module_imports - :members: diff --git a/docsrc/module_docs/py2store/ext/wordnet.rst b/docsrc/module_docs/py2store/ext/wordnet.rst deleted file mode 100644 index d8cb690..0000000 --- a/docsrc/module_docs/py2store/ext/wordnet.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.ext.wordnet -==================== -.. automodule:: py2store.ext.wordnet - :members: diff --git a/docsrc/module_docs/py2store/filesys.rst b/docsrc/module_docs/py2store/filesys.rst deleted file mode 100644 index ccd217b..0000000 --- a/docsrc/module_docs/py2store/filesys.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.filesys -================ -.. automodule:: py2store.filesys - :members: diff --git a/docsrc/module_docs/py2store/key_mappers.rst b/docsrc/module_docs/py2store/key_mappers.rst deleted file mode 100644 index b735bb9..0000000 --- a/docsrc/module_docs/py2store/key_mappers.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers -==================== -.. automodule:: py2store.key_mappers - :members: diff --git a/docsrc/module_docs/py2store/key_mappers/naming.rst b/docsrc/module_docs/py2store/key_mappers/naming.rst deleted file mode 100644 index e69557b..0000000 --- a/docsrc/module_docs/py2store/key_mappers/naming.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.naming -=========================== -.. automodule:: py2store.key_mappers.naming - :members: diff --git a/docsrc/module_docs/py2store/key_mappers/paths.rst b/docsrc/module_docs/py2store/key_mappers/paths.rst deleted file mode 100644 index d654d94..0000000 --- a/docsrc/module_docs/py2store/key_mappers/paths.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.paths -========================== -.. automodule:: py2store.key_mappers.paths - :members: diff --git a/docsrc/module_docs/py2store/key_mappers/str_utils.rst b/docsrc/module_docs/py2store/key_mappers/str_utils.rst deleted file mode 100644 index 781030b..0000000 --- a/docsrc/module_docs/py2store/key_mappers/str_utils.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.str_utils -============================== -.. automodule:: py2store.key_mappers.str_utils - :members: diff --git a/docsrc/module_docs/py2store/key_mappers/tuples.rst b/docsrc/module_docs/py2store/key_mappers/tuples.rst deleted file mode 100644 index aae63b9..0000000 --- a/docsrc/module_docs/py2store/key_mappers/tuples.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.key_mappers.tuples -=========================== -.. automodule:: py2store.key_mappers.tuples - :members: diff --git a/docsrc/module_docs/py2store/misc.rst b/docsrc/module_docs/py2store/misc.rst deleted file mode 100644 index 13e5be3..0000000 --- a/docsrc/module_docs/py2store/misc.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.misc -============= -.. automodule:: py2store.misc - :members: diff --git a/docsrc/module_docs/py2store/mixins.rst b/docsrc/module_docs/py2store/mixins.rst deleted file mode 100644 index d384129..0000000 --- a/docsrc/module_docs/py2store/mixins.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.mixins -=============== -.. automodule:: py2store.mixins - :members: diff --git a/docsrc/module_docs/py2store/my.rst b/docsrc/module_docs/py2store/my.rst deleted file mode 100644 index df5a72d..0000000 --- a/docsrc/module_docs/py2store/my.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.my -=========== -.. automodule:: py2store.my - :members: diff --git a/docsrc/module_docs/py2store/my/grabbers.rst b/docsrc/module_docs/py2store/my/grabbers.rst deleted file mode 100644 index 04d263c..0000000 --- a/docsrc/module_docs/py2store/my/grabbers.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.my.grabbers -==================== -.. automodule:: py2store.my.grabbers - :members: diff --git a/docsrc/module_docs/py2store/naming.rst b/docsrc/module_docs/py2store/naming.rst deleted file mode 100644 index 608b930..0000000 --- a/docsrc/module_docs/py2store/naming.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.naming -=============== -.. automodule:: py2store.naming - :members: diff --git a/docsrc/module_docs/py2store/parse_format.rst b/docsrc/module_docs/py2store/parse_format.rst deleted file mode 100644 index 1bca3a4..0000000 --- a/docsrc/module_docs/py2store/parse_format.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.parse_format -===================== -.. automodule:: py2store.parse_format - :members: diff --git a/docsrc/module_docs/py2store/paths.rst b/docsrc/module_docs/py2store/paths.rst deleted file mode 100644 index 595b16b..0000000 --- a/docsrc/module_docs/py2store/paths.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.paths -============== -.. automodule:: py2store.paths - :members: diff --git a/docsrc/module_docs/py2store/persisters.rst b/docsrc/module_docs/py2store/persisters.rst deleted file mode 100644 index d1d8fcf..0000000 --- a/docsrc/module_docs/py2store/persisters.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters -=================== -.. automodule:: py2store.persisters - :members: diff --git a/docsrc/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst b/docsrc/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst deleted file mode 100644 index 3f0e617..0000000 --- a/docsrc/module_docs/py2store/persisters/_postgres_w_psycopg2_in_progress.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters._postgres_w_psycopg2_in_progress -==================================================== -.. automodule:: py2store.persisters._postgres_w_psycopg2_in_progress - :members: diff --git a/docsrc/module_docs/py2store/persisters/arangodb_w_pyarango.rst b/docsrc/module_docs/py2store/persisters/arangodb_w_pyarango.rst deleted file mode 100644 index e99d97e..0000000 --- a/docsrc/module_docs/py2store/persisters/arangodb_w_pyarango.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.arangodb_w_pyarango -======================================= -.. automodule:: py2store.persisters.arangodb_w_pyarango - :members: diff --git a/docsrc/module_docs/py2store/persisters/couchdb_w_couchdb.rst b/docsrc/module_docs/py2store/persisters/couchdb_w_couchdb.rst deleted file mode 100644 index 567d04a..0000000 --- a/docsrc/module_docs/py2store/persisters/couchdb_w_couchdb.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.couchdb_w_couchdb -===================================== -.. automodule:: py2store.persisters.couchdb_w_couchdb - :members: diff --git a/docsrc/module_docs/py2store/persisters/dropbox_w_dropbox.rst b/docsrc/module_docs/py2store/persisters/dropbox_w_dropbox.rst deleted file mode 100644 index 5d5b625..0000000 --- a/docsrc/module_docs/py2store/persisters/dropbox_w_dropbox.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dropbox_w_dropbox -===================================== -.. automodule:: py2store.persisters.dropbox_w_dropbox - :members: diff --git a/docsrc/module_docs/py2store/persisters/dropbox_w_requests.rst b/docsrc/module_docs/py2store/persisters/dropbox_w_requests.rst deleted file mode 100644 index 0e5bf8f..0000000 --- a/docsrc/module_docs/py2store/persisters/dropbox_w_requests.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dropbox_w_requests -====================================== -.. automodule:: py2store.persisters.dropbox_w_requests - :members: diff --git a/docsrc/module_docs/py2store/persisters/dropbox_w_urllib.rst b/docsrc/module_docs/py2store/persisters/dropbox_w_urllib.rst deleted file mode 100644 index 7e700d9..0000000 --- a/docsrc/module_docs/py2store/persisters/dropbox_w_urllib.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dropbox_w_urllib -==================================== -.. automodule:: py2store.persisters.dropbox_w_urllib - :members: diff --git a/docsrc/module_docs/py2store/persisters/dynamodb_w_boto3.rst b/docsrc/module_docs/py2store/persisters/dynamodb_w_boto3.rst deleted file mode 100644 index 367f6b7..0000000 --- a/docsrc/module_docs/py2store/persisters/dynamodb_w_boto3.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.dynamodb_w_boto3 -==================================== -.. automodule:: py2store.persisters.dynamodb_w_boto3 - :members: diff --git a/docsrc/module_docs/py2store/persisters/ftp_persister.rst b/docsrc/module_docs/py2store/persisters/ftp_persister.rst deleted file mode 100644 index d13ef0d..0000000 --- a/docsrc/module_docs/py2store/persisters/ftp_persister.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.ftp_persister -================================= -.. automodule:: py2store.persisters.ftp_persister - :members: diff --git a/docsrc/module_docs/py2store/persisters/googledrive_w_pydrive.rst b/docsrc/module_docs/py2store/persisters/googledrive_w_pydrive.rst deleted file mode 100644 index 773b1cb..0000000 --- a/docsrc/module_docs/py2store/persisters/googledrive_w_pydrive.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.googledrive_w_pydrive -========================================= -.. automodule:: py2store.persisters.googledrive_w_pydrive - :members: diff --git a/docsrc/module_docs/py2store/persisters/local_files.rst b/docsrc/module_docs/py2store/persisters/local_files.rst deleted file mode 100644 index e2c9a1e..0000000 --- a/docsrc/module_docs/py2store/persisters/local_files.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.local_files -=============================== -.. automodule:: py2store.persisters.local_files - :members: diff --git a/docsrc/module_docs/py2store/persisters/mongo_w_pymongo.rst b/docsrc/module_docs/py2store/persisters/mongo_w_pymongo.rst deleted file mode 100644 index 49e503b..0000000 --- a/docsrc/module_docs/py2store/persisters/mongo_w_pymongo.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.mongo_w_pymongo -=================================== -.. automodule:: py2store.persisters.mongo_w_pymongo - :members: diff --git a/docsrc/module_docs/py2store/persisters/new_s3.rst b/docsrc/module_docs/py2store/persisters/new_s3.rst deleted file mode 100644 index 9dc0165..0000000 --- a/docsrc/module_docs/py2store/persisters/new_s3.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.new_s3 -========================== -.. automodule:: py2store.persisters.new_s3 - :members: diff --git a/docsrc/module_docs/py2store/persisters/redis_w_redis.rst b/docsrc/module_docs/py2store/persisters/redis_w_redis.rst deleted file mode 100644 index c493e04..0000000 --- a/docsrc/module_docs/py2store/persisters/redis_w_redis.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.redis_w_redis -================================= -.. automodule:: py2store.persisters.redis_w_redis - :members: diff --git a/docsrc/module_docs/py2store/persisters/s3_w_boto3.rst b/docsrc/module_docs/py2store/persisters/s3_w_boto3.rst deleted file mode 100644 index 7d4d2ab..0000000 --- a/docsrc/module_docs/py2store/persisters/s3_w_boto3.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.s3_w_boto3 -============================== -.. automodule:: py2store.persisters.s3_w_boto3 - :members: diff --git a/docsrc/module_docs/py2store/persisters/sql_w_odbc.rst b/docsrc/module_docs/py2store/persisters/sql_w_odbc.rst deleted file mode 100644 index 535b0a3..0000000 --- a/docsrc/module_docs/py2store/persisters/sql_w_odbc.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.sql_w_odbc -============================== -.. automodule:: py2store.persisters.sql_w_odbc - :members: diff --git a/docsrc/module_docs/py2store/persisters/sql_w_sqlalchemy.rst b/docsrc/module_docs/py2store/persisters/sql_w_sqlalchemy.rst deleted file mode 100644 index c45bd23..0000000 --- a/docsrc/module_docs/py2store/persisters/sql_w_sqlalchemy.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.sql_w_sqlalchemy -==================================== -.. automodule:: py2store.persisters.sql_w_sqlalchemy - :members: diff --git a/docsrc/module_docs/py2store/persisters/ssh_persister.rst b/docsrc/module_docs/py2store/persisters/ssh_persister.rst deleted file mode 100644 index d3dc03e..0000000 --- a/docsrc/module_docs/py2store/persisters/ssh_persister.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.ssh_persister -================================= -.. automodule:: py2store.persisters.ssh_persister - :members: diff --git a/docsrc/module_docs/py2store/persisters/w_aiofile.rst b/docsrc/module_docs/py2store/persisters/w_aiofile.rst deleted file mode 100644 index 55495f1..0000000 --- a/docsrc/module_docs/py2store/persisters/w_aiofile.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.persisters.w_aiofile -============================= -.. automodule:: py2store.persisters.w_aiofile - :members: diff --git a/docsrc/module_docs/py2store/scrap/new_gen_local.rst b/docsrc/module_docs/py2store/scrap/new_gen_local.rst deleted file mode 100644 index 7fdd498..0000000 --- a/docsrc/module_docs/py2store/scrap/new_gen_local.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.scrap.new_gen_local -============================ -.. automodule:: py2store.scrap.new_gen_local - :members: diff --git a/docsrc/module_docs/py2store/serializers.rst b/docsrc/module_docs/py2store/serializers.rst deleted file mode 100644 index 50e0f0c..0000000 --- a/docsrc/module_docs/py2store/serializers.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers -==================== -.. automodule:: py2store.serializers - :members: diff --git a/docsrc/module_docs/py2store/serializers/audio.rst b/docsrc/module_docs/py2store/serializers/audio.rst deleted file mode 100644 index aea18fc..0000000 --- a/docsrc/module_docs/py2store/serializers/audio.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.audio -========================== -.. automodule:: py2store.serializers.audio - :members: diff --git a/docsrc/module_docs/py2store/serializers/jsonization.rst b/docsrc/module_docs/py2store/serializers/jsonization.rst deleted file mode 100644 index 5213326..0000000 --- a/docsrc/module_docs/py2store/serializers/jsonization.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.jsonization -================================ -.. automodule:: py2store.serializers.jsonization - :members: diff --git a/docsrc/module_docs/py2store/serializers/pickled.rst b/docsrc/module_docs/py2store/serializers/pickled.rst deleted file mode 100644 index 8504d7c..0000000 --- a/docsrc/module_docs/py2store/serializers/pickled.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.pickled -============================ -.. automodule:: py2store.serializers.pickled - :members: diff --git a/docsrc/module_docs/py2store/serializers/regular_panel_data.rst b/docsrc/module_docs/py2store/serializers/regular_panel_data.rst deleted file mode 100644 index 3ad84b1..0000000 --- a/docsrc/module_docs/py2store/serializers/regular_panel_data.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.regular_panel_data -======================================= -.. automodule:: py2store.serializers.regular_panel_data - :members: diff --git a/docsrc/module_docs/py2store/serializers/sequential.rst b/docsrc/module_docs/py2store/serializers/sequential.rst deleted file mode 100644 index 1e9999a..0000000 --- a/docsrc/module_docs/py2store/serializers/sequential.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.serializers.sequential -=============================== -.. automodule:: py2store.serializers.sequential - :members: diff --git a/docsrc/module_docs/py2store/signatures.rst b/docsrc/module_docs/py2store/signatures.rst deleted file mode 100644 index fd0c880..0000000 --- a/docsrc/module_docs/py2store/signatures.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.signatures -=================== -.. automodule:: py2store.signatures - :members: diff --git a/docsrc/module_docs/py2store/slib.rst b/docsrc/module_docs/py2store/slib.rst deleted file mode 100644 index 73997c3..0000000 --- a/docsrc/module_docs/py2store/slib.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.slib -============= -.. automodule:: py2store.slib - :members: diff --git a/docsrc/module_docs/py2store/slib/s_configparser.rst b/docsrc/module_docs/py2store/slib/s_configparser.rst deleted file mode 100644 index 6f80aef..0000000 --- a/docsrc/module_docs/py2store/slib/s_configparser.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.slib.s_configparser -============================ -.. automodule:: py2store.slib.s_configparser - :members: diff --git a/docsrc/module_docs/py2store/slib/s_zipfile.rst b/docsrc/module_docs/py2store/slib/s_zipfile.rst deleted file mode 100644 index 006b836..0000000 --- a/docsrc/module_docs/py2store/slib/s_zipfile.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.slib.s_zipfile -======================= -.. automodule:: py2store.slib.s_zipfile - :members: diff --git a/docsrc/module_docs/py2store/sources.rst b/docsrc/module_docs/py2store/sources.rst deleted file mode 100644 index f4b2f9a..0000000 --- a/docsrc/module_docs/py2store/sources.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.sources -================ -.. automodule:: py2store.sources - :members: diff --git a/docsrc/module_docs/py2store/stores.rst b/docsrc/module_docs/py2store/stores.rst deleted file mode 100644 index f3044d0..0000000 --- a/docsrc/module_docs/py2store/stores.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores -=============== -.. automodule:: py2store.stores - :members: diff --git a/docsrc/module_docs/py2store/stores/arangodb_store.rst b/docsrc/module_docs/py2store/stores/arangodb_store.rst deleted file mode 100644 index 412ab51..0000000 --- a/docsrc/module_docs/py2store/stores/arangodb_store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.arangodb_store -============================== -.. automodule:: py2store.stores.arangodb_store - :members: diff --git a/docsrc/module_docs/py2store/stores/couchdb_store.rst b/docsrc/module_docs/py2store/stores/couchdb_store.rst deleted file mode 100644 index 8b28730..0000000 --- a/docsrc/module_docs/py2store/stores/couchdb_store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.couchdb_store -============================= -.. automodule:: py2store.stores.couchdb_store - :members: diff --git a/docsrc/module_docs/py2store/stores/delegation_stores.rst b/docsrc/module_docs/py2store/stores/delegation_stores.rst deleted file mode 100644 index 410755f..0000000 --- a/docsrc/module_docs/py2store/stores/delegation_stores.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.delegation_stores -================================= -.. automodule:: py2store.stores.delegation_stores - :members: diff --git a/docsrc/module_docs/py2store/stores/dropbox_store.rst b/docsrc/module_docs/py2store/stores/dropbox_store.rst deleted file mode 100644 index 6daec65..0000000 --- a/docsrc/module_docs/py2store/stores/dropbox_store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.dropbox_store -============================= -.. automodule:: py2store.stores.dropbox_store - :members: diff --git a/docsrc/module_docs/py2store/stores/local_store.rst b/docsrc/module_docs/py2store/stores/local_store.rst deleted file mode 100644 index 38cad28..0000000 --- a/docsrc/module_docs/py2store/stores/local_store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.local_store -=========================== -.. automodule:: py2store.stores.local_store - :members: diff --git a/docsrc/module_docs/py2store/stores/mongo_store.rst b/docsrc/module_docs/py2store/stores/mongo_store.rst deleted file mode 100644 index f9da813..0000000 --- a/docsrc/module_docs/py2store/stores/mongo_store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.mongo_store -=========================== -.. automodule:: py2store.stores.mongo_store - :members: diff --git a/docsrc/module_docs/py2store/stores/s3_store.rst b/docsrc/module_docs/py2store/stores/s3_store.rst deleted file mode 100644 index f93f4d1..0000000 --- a/docsrc/module_docs/py2store/stores/s3_store.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.s3_store -======================== -.. automodule:: py2store.stores.s3_store - :members: diff --git a/docsrc/module_docs/py2store/stores/sql_w_sqlalchemy.rst b/docsrc/module_docs/py2store/stores/sql_w_sqlalchemy.rst deleted file mode 100644 index 2e33988..0000000 --- a/docsrc/module_docs/py2store/stores/sql_w_sqlalchemy.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.stores.sql_w_sqlalchemy -================================ -.. automodule:: py2store.stores.sql_w_sqlalchemy - :members: diff --git a/docsrc/module_docs/py2store/test.rst b/docsrc/module_docs/py2store/test.rst deleted file mode 100644 index a42a106..0000000 --- a/docsrc/module_docs/py2store/test.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test -============= -.. automodule:: py2store.test - :members: diff --git a/docsrc/module_docs/py2store/test/local_files_test.rst b/docsrc/module_docs/py2store/test/local_files_test.rst deleted file mode 100644 index 16f9f49..0000000 --- a/docsrc/module_docs/py2store/test/local_files_test.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.local_files_test -============================== -.. automodule:: py2store.test.local_files_test - :members: diff --git a/docsrc/module_docs/py2store/test/quick_test.rst b/docsrc/module_docs/py2store/test/quick_test.rst deleted file mode 100644 index a7585cc..0000000 --- a/docsrc/module_docs/py2store/test/quick_test.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.quick_test -======================== -.. automodule:: py2store.test.quick_test - :members: diff --git a/docsrc/module_docs/py2store/test/scrap.rst b/docsrc/module_docs/py2store/test/scrap.rst deleted file mode 100644 index ff6d70e..0000000 --- a/docsrc/module_docs/py2store/test/scrap.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.scrap -=================== -.. automodule:: py2store.test.scrap - :members: diff --git a/docsrc/module_docs/py2store/test/simple_test.rst b/docsrc/module_docs/py2store/test/simple_test.rst deleted file mode 100644 index 3953ebc..0000000 --- a/docsrc/module_docs/py2store/test/simple_test.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.simple_test -========================= -.. automodule:: py2store.test.simple_test - :members: diff --git a/docsrc/module_docs/py2store/test/trans_test.rst b/docsrc/module_docs/py2store/test/trans_test.rst deleted file mode 100644 index 8b8cca2..0000000 --- a/docsrc/module_docs/py2store/test/trans_test.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.trans_test -======================== -.. automodule:: py2store.test.trans_test - :members: diff --git a/docsrc/module_docs/py2store/test/util.rst b/docsrc/module_docs/py2store/test/util.rst deleted file mode 100644 index c4f99b2..0000000 --- a/docsrc/module_docs/py2store/test/util.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.test.util -================== -.. automodule:: py2store.test.util - :members: diff --git a/docsrc/module_docs/py2store/trans.rst b/docsrc/module_docs/py2store/trans.rst deleted file mode 100644 index e5d9c12..0000000 --- a/docsrc/module_docs/py2store/trans.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.trans -============== -.. automodule:: py2store.trans - :members: diff --git a/docsrc/module_docs/py2store/util.rst b/docsrc/module_docs/py2store/util.rst deleted file mode 100644 index aeedca3..0000000 --- a/docsrc/module_docs/py2store/util.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.util -============= -.. automodule:: py2store.util - :members: diff --git a/docsrc/module_docs/py2store/utils.rst b/docsrc/module_docs/py2store/utils.rst deleted file mode 100644 index 60edf44..0000000 --- a/docsrc/module_docs/py2store/utils.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils -============== -.. automodule:: py2store.utils - :members: diff --git a/docsrc/module_docs/py2store/utils/affine_conversion.rst b/docsrc/module_docs/py2store/utils/affine_conversion.rst deleted file mode 100644 index 7838f38..0000000 --- a/docsrc/module_docs/py2store/utils/affine_conversion.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.affine_conversion -================================ -.. automodule:: py2store.utils.affine_conversion - :members: diff --git a/docsrc/module_docs/py2store/utils/appendable.rst b/docsrc/module_docs/py2store/utils/appendable.rst deleted file mode 100644 index 68d3310..0000000 --- a/docsrc/module_docs/py2store/utils/appendable.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.appendable -========================= -.. automodule:: py2store.utils.appendable - :members: diff --git a/docsrc/module_docs/py2store/utils/attr_dict.rst b/docsrc/module_docs/py2store/utils/attr_dict.rst deleted file mode 100644 index 70d27c9..0000000 --- a/docsrc/module_docs/py2store/utils/attr_dict.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.attr_dict -======================== -.. automodule:: py2store.utils.attr_dict - :members: diff --git a/docsrc/module_docs/py2store/utils/cache_descriptors.rst b/docsrc/module_docs/py2store/utils/cache_descriptors.rst deleted file mode 100644 index 0c2ca58..0000000 --- a/docsrc/module_docs/py2store/utils/cache_descriptors.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.cache_descriptors -================================ -.. automodule:: py2store.utils.cache_descriptors - :members: diff --git a/docsrc/module_docs/py2store/utils/cumul_aggreg_write.rst b/docsrc/module_docs/py2store/utils/cumul_aggreg_write.rst deleted file mode 100644 index 468131e..0000000 --- a/docsrc/module_docs/py2store/utils/cumul_aggreg_write.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.cumul_aggreg_write -================================= -.. automodule:: py2store.utils.cumul_aggreg_write - :members: diff --git a/docsrc/module_docs/py2store/utils/explicit.rst b/docsrc/module_docs/py2store/utils/explicit.rst deleted file mode 100644 index ddbfe2c..0000000 --- a/docsrc/module_docs/py2store/utils/explicit.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.explicit -======================= -.. automodule:: py2store.utils.explicit - :members: diff --git a/docsrc/module_docs/py2store/utils/glom.rst b/docsrc/module_docs/py2store/utils/glom.rst deleted file mode 100644 index f911226..0000000 --- a/docsrc/module_docs/py2store/utils/glom.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.glom -=================== -.. automodule:: py2store.utils.glom - :members: diff --git a/docsrc/module_docs/py2store/utils/mappify.rst b/docsrc/module_docs/py2store/utils/mappify.rst deleted file mode 100644 index a67657f..0000000 --- a/docsrc/module_docs/py2store/utils/mappify.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.mappify -====================== -.. automodule:: py2store.utils.mappify - :members: diff --git a/docsrc/module_docs/py2store/utils/mg_selectors.rst b/docsrc/module_docs/py2store/utils/mg_selectors.rst deleted file mode 100644 index d60184a..0000000 --- a/docsrc/module_docs/py2store/utils/mg_selectors.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.mg_selectors -=========================== -.. automodule:: py2store.utils.mg_selectors - :members: diff --git a/docsrc/module_docs/py2store/utils/mongoquery.rst b/docsrc/module_docs/py2store/utils/mongoquery.rst deleted file mode 100644 index 811d593..0000000 --- a/docsrc/module_docs/py2store/utils/mongoquery.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.mongoquery -========================= -.. automodule:: py2store.utils.mongoquery - :members: diff --git a/docsrc/module_docs/py2store/utils/signatures.rst b/docsrc/module_docs/py2store/utils/signatures.rst deleted file mode 100644 index 86d6bf3..0000000 --- a/docsrc/module_docs/py2store/utils/signatures.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.signatures -========================= -.. automodule:: py2store.utils.signatures - :members: diff --git a/docsrc/module_docs/py2store/utils/sliceable.rst b/docsrc/module_docs/py2store/utils/sliceable.rst deleted file mode 100644 index d3951af..0000000 --- a/docsrc/module_docs/py2store/utils/sliceable.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.sliceable -======================== -.. automodule:: py2store.utils.sliceable - :members: diff --git a/docsrc/module_docs/py2store/utils/timeseries_caching.rst b/docsrc/module_docs/py2store/utils/timeseries_caching.rst deleted file mode 100644 index 83598a1..0000000 --- a/docsrc/module_docs/py2store/utils/timeseries_caching.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.timeseries_caching -================================= -.. automodule:: py2store.utils.timeseries_caching - :members: diff --git a/docsrc/module_docs/py2store/utils/uri_utils.rst b/docsrc/module_docs/py2store/utils/uri_utils.rst deleted file mode 100644 index d60c81e..0000000 --- a/docsrc/module_docs/py2store/utils/uri_utils.rst +++ /dev/null @@ -1,4 +0,0 @@ -py2store.utils.uri_utils -======================== -.. automodule:: py2store.utils.uri_utils - :members: diff --git a/docsrc/requirements.txt b/docsrc/requirements.txt deleted file mode 100644 index a84d1a8..0000000 --- a/docsrc/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Sphinx -recommonmark \ No newline at end of file diff --git a/docsrc/table_of_contents.rst b/docsrc/table_of_contents.rst deleted file mode 100644 index 54e1b13..0000000 --- a/docsrc/table_of_contents.rst +++ /dev/null @@ -1,80 +0,0 @@ -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - module_docs/py2store - module_docs/py2store/access - module_docs/py2store/appendable - module_docs/py2store/base - module_docs/py2store/caching - module_docs/py2store/dig - module_docs/py2store/errors - module_docs/py2store/examples - module_docs/py2store/examples/dropbox_w_urllib - module_docs/py2store/examples/kv_walking - module_docs/py2store/examples/last_key_inserted - module_docs/py2store/examples/python_code_stats - module_docs/py2store/examples/write_caches - module_docs/py2store/ext - module_docs/py2store/ext/dataframes - module_docs/py2store/ext/docx - module_docs/py2store/ext/github - module_docs/py2store/ext/gitlab - module_docs/py2store/ext/hdf - module_docs/py2store/ext/matlab - module_docs/py2store/ext/wordnet - module_docs/py2store/filesys - module_docs/py2store/key_mappers - module_docs/py2store/key_mappers/naming - module_docs/py2store/key_mappers/paths - module_docs/py2store/key_mappers/str_utils - module_docs/py2store/key_mappers/tuples - module_docs/py2store/misc - module_docs/py2store/mixins - module_docs/py2store/my - module_docs/py2store/my/grabbers - module_docs/py2store/naming - module_docs/py2store/parse_format - module_docs/py2store/paths - module_docs/py2store/persisters - module_docs/py2store/persisters/dropbox_w_dropbox - module_docs/py2store/persisters/googledrive_w_pydrive - module_docs/py2store/persisters/local_files - module_docs/py2store/persisters/new_s3 - module_docs/py2store/persisters/redis_w_redis - module_docs/py2store/persisters/s3_w_boto3 - module_docs/py2store/persisters/sql_w_sqlalchemy - module_docs/py2store/persisters/w_aiofile - module_docs/py2store/serializers - module_docs/py2store/serializers/pickled - module_docs/py2store/signatures - module_docs/py2store/slib - module_docs/py2store/slib/s_configparser - module_docs/py2store/slib/s_zipfile - module_docs/py2store/sources - module_docs/py2store/stores - module_docs/py2store/stores/dropbox_store - module_docs/py2store/stores/local_store - module_docs/py2store/stores/s3_store - module_docs/py2store/stores/sql_w_sqlalchemy - module_docs/py2store/test - module_docs/py2store/test/local_files_test - module_docs/py2store/test/quick_test - module_docs/py2store/test/scrap - module_docs/py2store/test/util - module_docs/py2store/trans - module_docs/py2store/util - module_docs/py2store/utils - module_docs/py2store/utils/affine_conversion - module_docs/py2store/utils/appendable - module_docs/py2store/utils/attr_dict - module_docs/py2store/utils/cache_descriptors - module_docs/py2store/utils/cumul_aggreg_write - module_docs/py2store/utils/explicit - module_docs/py2store/utils/glom - module_docs/py2store/utils/mappify - module_docs/py2store/utils/mg_selectors - module_docs/py2store/utils/mongoquery - module_docs/py2store/utils/signatures - module_docs/py2store/utils/timeseries_caching - module_docs/py2store/utils/uri_utils diff --git a/docsrc/test.rst b/docsrc/test.rst deleted file mode 100644 index aabeb77..0000000 --- a/docsrc/test.rst +++ /dev/null @@ -1,494 +0,0 @@ -py2store.filesys -================ -.. automodule:: py2store.filesys - :members: - -py2store.misc -============= -.. automodule:: py2store.misc - :members: - -py2store.mixins -=============== -.. automodule:: py2store.mixins - :members: - -py2store.test.util -================== -.. automodule:: py2store.test.util - :members: - -py2store.test.quick -=================== -.. automodule:: py2store.test.quick - :members: - -py2store.test -============= -.. automodule:: py2store.test - :members: - -py2store.test.simple -==================== -.. automodule:: py2store.test.simple - :members: - -py2store.test.scrap -=================== -.. automodule:: py2store.test.scrap - :members: - -py2store.util -============= -.. automodule:: py2store.util - :members: - -py2store.ext.docx -================= -.. automodule:: py2store.ext.docx - :members: - -py2store.ext.gitlab -=================== -.. automodule:: py2store.ext.gitlab - :members: - -py2store.ext.hdf -================ -.. automodule:: py2store.ext.hdf - :members: - -py2store.ext -============ -.. automodule:: py2store.ext - :members: - -py2store.ext.matlab -=================== -.. automodule:: py2store.ext.matlab - :members: - -py2store.ext.kaggle -=================== -.. automodule:: py2store.ext.kaggle - :members: - -py2store.ext.module_imports -=========================== -.. automodule:: py2store.ext.module_imports - :members: - -py2store.ext.audio -================== -.. automodule:: py2store.ext.audio - :members: - -py2store.ext.github -=================== -.. automodule:: py2store.ext.github - :members: - -py2store.ext.dataframes -======================= -.. automodule:: py2store.ext.dataframes - :members: - -py2store.access -=============== -.. automodule:: py2store.access - :members: - -py2store.__init__ -================= -.. automodule:: py2store.__init__ - :members: - -py2store.stores.s3_store -======================== -.. automodule:: py2store.stores.s3_store - :members: - -py2store.stores.delegation_stores -================================= -.. automodule:: py2store.stores.delegation_stores - :members: - -py2store.stores.sql_w_sqlalchemy -================================ -.. automodule:: py2store.stores.sql_w_sqlalchemy - :members: - -py2store.stores.arangodb_store -============================== -.. automodule:: py2store.stores.arangodb_store - :members: - -py2store.stores.dropbox_store -============================= -.. automodule:: py2store.stores.dropbox_store - :members: - -py2store.stores.local_store -=========================== -.. automodule:: py2store.stores.local_store - :members: - -py2store.stores -=============== -.. automodule:: py2store.stores - :members: - -py2store.stores.couchdb_store -============================= -.. automodule:: py2store.stores.couchdb_store - :members: - -py2store.stores.mongo_store -=========================== -.. automodule:: py2store.stores.mongo_store - :members: - -py2store.core -============= -.. automodule:: py2store.core - :members: - -py2store.utils.uri_utils -======================== -.. automodule:: py2store.utils.uri_utils - :members: - -py2store.utils.explicit -======================= -.. automodule:: py2store.utils.explicit - :members: - -py2store.utils.timeseries_caching -================================= -.. automodule:: py2store.utils.timeseries_caching - :members: - -py2store.utils.attr_dict.py.attr_dict -===================================== -.. automodule:: py2store.utils.attr_dict.py.attr_dict - :members: - -py2store.utils.attr_dict.py -=========================== -.. automodule:: py2store.utils.attr_dict.py - :members: - -py2store.utils.cumul_aggreg_write -================================= -.. automodule:: py2store.utils.cumul_aggreg_write - :members: - -py2store.utils -============== -.. automodule:: py2store.utils - :members: - -py2store.utils.cache_descriptors -================================ -.. automodule:: py2store.utils.cache_descriptors - :members: - -py2store.utils.appendable -========================= -.. automodule:: py2store.utils.appendable - :members: - -py2store.utils.affine_conversion -================================ -.. automodule:: py2store.utils.affine_conversion - :members: - -py2store.utils.signatures -========================= -.. automodule:: py2store.utils.signatures - :members: - -py2store.utils.sliceable -======================== -.. automodule:: py2store.utils.sliceable - :members: - -py2store.utils.mappify -====================== -.. automodule:: py2store.utils.mappify - :members: - -py2store.utils.glom -=================== -.. automodule:: py2store.utils.glom - :members: - -py2store.persisters.sql_w_odbc -============================== -.. automodule:: py2store.persisters.sql_w_odbc - :members: - -py2store.persisters.dynamodb_w_boto3 -==================================== -.. automodule:: py2store.persisters.dynamodb_w_boto3 - :members: - -py2store.persisters.couchdb_w_couchdb -===================================== -.. automodule:: py2store.persisters.couchdb_w_couchdb - :members: - -py2store.persisters.ftp_persister -================================= -.. automodule:: py2store.persisters.ftp_persister - :members: - -py2store.persisters.dropbox_w_urllib -==================================== -.. automodule:: py2store.persisters.dropbox_w_urllib - :members: - -py2store.persisters._google_drive_in_progress -============================================= -.. automodule:: py2store.persisters._google_drive_in_progress - :members: - -py2store.persisters.dropbox_w_dropbox -===================================== -.. automodule:: py2store.persisters.dropbox_w_dropbox - :members: - -py2store.persisters.redis_w_redis -================================= -.. automodule:: py2store.persisters.redis_w_redis - :members: - -py2store.persisters.sql_w_sqlalchemy -==================================== -.. automodule:: py2store.persisters.sql_w_sqlalchemy - :members: - -py2store.persisters.new_s3 -========================== -.. automodule:: py2store.persisters.new_s3 - :members: - -py2store.persisters -=================== -.. automodule:: py2store.persisters - :members: - -py2store.persisters.dropbox_w_requests -====================================== -.. automodule:: py2store.persisters.dropbox_w_requests - :members: - -py2store.persisters.w_aiofile -============================= -.. automodule:: py2store.persisters.w_aiofile - :members: - -py2store.persisters.local_files -=============================== -.. automodule:: py2store.persisters.local_files - :members: - -py2store.persisters.arangodb_w_pyarango -======================================= -.. automodule:: py2store.persisters.arangodb_w_pyarango - :members: - -py2store.persisters._cassandra_in_progress -========================================== -.. automodule:: py2store.persisters._cassandra_in_progress - :members: - -py2store.persisters._couchdb_in_progress -======================================== -.. automodule:: py2store.persisters._couchdb_in_progress - :members: - -py2store.persisters.s3_w_boto3 -============================== -.. automodule:: py2store.persisters.s3_w_boto3 - :members: - -py2store.persisters._postgres_w_psycopg2_in_progress -==================================================== -.. automodule:: py2store.persisters._postgres_w_psycopg2_in_progress - :members: - -py2store.persisters.ssh_persister -================================= -.. automodule:: py2store.persisters.ssh_persister - :members: - -py2store.persisters.mongo_w_pymongo -=================================== -.. automodule:: py2store.persisters.mongo_w_pymongo - :members: - -py2store.persisters.googledrive_w_pydrive -========================================= -.. automodule:: py2store.persisters.googledrive_w_pydrive - :members: - -py2store.sources -================ -.. automodule:: py2store.sources - :members: - -py2store.dig -============ -.. automodule:: py2store.dig - :members: - -py2store.serializers.pickled -============================ -.. automodule:: py2store.serializers.pickled - :members: - -py2store.serializers.jsonization -================================ -.. automodule:: py2store.serializers.jsonization - :members: - -py2store.serializers -==================== -.. automodule:: py2store.serializers - :members: - -py2store.serializers.sequential -=============================== -.. automodule:: py2store.serializers.sequential - :members: - -py2store.serializers.regular_panel_data -======================================= -.. automodule:: py2store.serializers.regular_panel_data - :members: - -py2store.serializers.audio -========================== -.. automodule:: py2store.serializers.audio - :members: - -py2store.caching -================ -.. automodule:: py2store.caching - :members: - -py2store.scrap -============== -.. automodule:: py2store.scrap - :members: - -py2store.scrap.new_gen_local -============================ -.. automodule:: py2store.scrap.new_gen_local - :members: - -py2store.examples.write_caches -============================== -.. automodule:: py2store.examples.write_caches - :members: - -py2store.examples -================= -.. automodule:: py2store.examples - :members: - -py2store.examples.python_code_stats -=================================== -.. automodule:: py2store.examples.python_code_stats - :members: - -py2store.examples.kv_walking -============================ -.. automodule:: py2store.examples.kv_walking - :members: - -py2store.my -=========== -.. automodule:: py2store.my - :members: - -py2store.my.grabbers -==================== -.. automodule:: py2store.my.grabbers - :members: - -py2store.trans -============== -.. automodule:: py2store.trans - :members: - -py2store.key_mappers.str_utils -============================== -.. automodule:: py2store.key_mappers.str_utils - :members: - -py2store.key_mappers.tuples -=========================== -.. automodule:: py2store.key_mappers.tuples - :members: - -py2store.key_mappers.paths -========================== -.. automodule:: py2store.key_mappers.paths - :members: - -py2store.key_mappers.naming -=========================== -.. automodule:: py2store.key_mappers.naming - :members: - -py2store.key_mappers -==================== -.. automodule:: py2store.key_mappers - :members: - -py2store.errors -=============== -.. automodule:: py2store.errors - :members: - -py2store.slib.s_configparser -============================ -.. automodule:: py2store.slib.s_configparser - :members: - -py2store.slib -============= -.. automodule:: py2store.slib - :members: - -py2store.slib.s_zipfile -======================= -.. automodule:: py2store.slib.s_zipfile - :members: - -py2store.base -============= -.. automodule:: py2store.base - :members: - -py2store.selectors.mg_selectors -=============================== -.. automodule:: py2store.selectors.mg_selectors - :members: - -py2store.selectors.mongoquery -============================= -.. automodule:: py2store.selectors.mongoquery - :members: - -py2store.selectors -================== -.. automodule:: py2store.selectors - :members: - -py2store.parse_format -===================== -.. automodule:: py2store.parse_format - :members: diff --git a/docs/_sources/how_to.md.txt b/misc/docs/zip_files_how_to.md similarity index 100% rename from docs/_sources/how_to.md.txt rename to misc/docs/zip_files_how_to.md diff --git a/py2store/__init__.py b/py2store/__init__.py index 00114a7..462c8f8 100644 --- a/py2store/__init__.py +++ b/py2store/__init__.py @@ -1,5 +1,21 @@ """ -Your portal to many Data Object Layer goodies +py2store: tools to create simple and consistent interfaces to complicated and varied data sources. + +The core has moved to the ``dol`` package (Data Object Layer); py2store keeps the original +names, re-exports them, and keeps the local-file stores that still live here. A store is a +``MutableMapping`` whose keys and values are transformed on the way in and out, so that files, +zip archives or databases are read and written like a ``dict``. + +Main entry points: + +- ``LocalTextStore``, ``LocalBinaryStore``, ``LocalPickleStore``, ``LocalJsonStore``: the files under a root directory as a dict +- ``QuickStore``: the pickle store with a temporary default root and directories created on write +- ``wrap_kvs``, ``filt_iter``, ``cached_keys``: transform a store's keys, values or iteration (from ``dol.trans``) +- ``kvhead``, ``ihead``: peek at the first items of a store or an iterable + +>>> from py2store import kvhead +>>> kvhead({'a': 1, 'b': 2}) +('a', 1) """ import os from contextlib import suppress @@ -8,7 +24,18 @@ def kvhead(store, n=1): - """Get the first item of a kv store, or a list of the first n items""" + """Get the first ``(key, value)`` item of a store, or a list of the first ``n`` items. + + With ``n=1`` the item itself is returned (``None`` if the store is empty); otherwise a + list of at most ``n`` items, in the store's iteration order. + + >>> kvhead({'a': 1, 'b': 2}) + ('a', 1) + >>> kvhead({'a': 1, 'b': 2}, 5) + [('a', 1), ('b', 2)] + >>> kvhead({}) is None + True + """ if n == 1: for k in store: return k, store[k] @@ -17,7 +44,18 @@ def kvhead(store, n=1): def ihead(store, n=1): - """Get the first item of an iterable, or a list of the first n items""" + """Get the first item of an iterable, or a list of the first ``n`` items. + + With ``n=1`` the item itself is returned (``None`` if the iterable is empty); otherwise a + list of at most ``n`` items. + + >>> ihead(iter('abc')) + 'a' + >>> ihead('abc', 2) + ['a', 'b'] + >>> ihead(iter('')) is None + True + """ if n == 1: for item in iter(store): return item diff --git a/py2store/access.py b/py2store/access.py index d681d04..ddff2a7 100644 --- a/py2store/access.py +++ b/py2store/access.py @@ -11,15 +11,19 @@ There are two main key-value stores: One for configurations the user wants to reuse, and the other for the user's desired defaults. Both have the same structure: + * first level key: Name of the resource (should be a valid python variable name) * The reminder is more or less free form (until the day we lay out some schemas for this) The system will look for the specification of user_configs and user_defaults in a json file. The filepath to this json file can specified in environment variables PY2STORE_CONFIGS_JSON_FILEPATH and PY2STORE_DEFAULTS_JSON_FILEPATH + respectively. -By default, they are: +By default, they are:: + ~/.py2store_configs.json and ~/.py2store_defaults.json + respectively. """ import os @@ -48,6 +52,7 @@ def getenv(name, default=None): def assert_callable(f: callable) -> callable: + """Return ``f`` unchanged if it is callable, else raise ``AssertionError``.""" assert callable(f), f'Is not callable: {f}' return f @@ -96,10 +101,23 @@ def _fakit(f: callable, a: (tuple, list), k: dict): def fakit_from_dict(d, func_loader=assert_callable): + """Call the function in ``d['f']`` (through ``func_loader``) with the args ``d['a']`` and kwargs ``d['k']``, both optional.""" return _fakit(func_loader(d['f']), a=d.get('a', ()), k=d.get('k', {})) def fakit_from_tuple(t: (tuple, list), func_loader: callable = dflt_func_loader): + """Call the function in ``t[0]`` (through ``func_loader``) with the args and kwargs in the rest of ``t``. + + ``t`` has 1 to 3 elements: ``(f,)``, ``(f, args)``, ``(f, kwargs)`` or ``(f, args, kwargs)``, + where ``args`` is a tuple or list and ``kwargs`` a dict. + + >>> fakit_from_tuple((len, ['abc'])) + 3 + >>> fakit_from_tuple(('builtins.len', ['ab'])) + 2 + >>> fakit_from_tuple((dict, (), {'x': 1})) + {'x': 1} + """ f = func_loader(t[0]) a = () k = {} @@ -132,14 +150,15 @@ def fakit_from_tuple(t: (tuple, list), func_loader: callable = dflt_func_loader) def fakit(fak, func_loader=dflt_func_loader): """Execute a fak with given f, a, k and function loader. - Essentially returns func_loader(f)(*a, **k) + Essentially returns ``func_loader(f)(*a, **k)`` Args: fak: A (f, a, k) specification. Could be a tuple or a dict (with 'f', 'a', 'k' keys). All but f are optional. func_loader: A function returning a function. This is where you specify any validation of func specification f, and/or how to get a callable from it. - Returns: A python object. + Returns: + A python object. """ if isinstance(fak, dict): @@ -160,6 +179,7 @@ def fakit(fak, func_loader=dflt_func_loader): def mkdir_if_needed(dirpath, name=None, verbose=True): + """Create ``dirpath`` if it does not exist, printing a note that calls it ``name`` (``verbose`` is accepted but not used).""" if not os.path.isdir(dirpath): name = name or 'directory' print(f"The {name} doesn't exist. Making it: {dirpath}") @@ -185,6 +205,7 @@ def mkdir_if_needed(dirpath, name=None, verbose=True): if os.path.isdir(user_configs_dirpath): def directory_json_items(): + """Yield ``(name, contents)`` for every ``.json`` file in the user configs directory, warning about files that fail to decode.""" for f in filter( lambda x: x.endswith('.json'), os.listdir(user_configs_dirpath) ): @@ -216,6 +237,12 @@ def directory_json_items(): @OverWritesNotAllowedMixin.wrap class MyConfigs(MiscStoreMixin, LocalBinaryStore): + """The user's config files under the ``my`` configs directory, as a store. + + A ``'name:key:subkey'`` key drills into the nested values of the ``name`` config. + Overwriting and deleting entries are disabled, to keep the configs safe. + """ + key_sep = ':' @wraps(LocalBinaryStore) @@ -234,6 +261,7 @@ def refresh(self): self.__init__(*args, **kwargs) def get(self, k, default=None): + """``self[k]``, or ``default`` if the key is missing.""" try: return self[k] except KeyError: @@ -253,6 +281,7 @@ def __getitem__(self, k): ) def get_config_value(self, k, path=None): + """The config named ``k``, or its ``path`` entry when ``path`` is given (``KeyError`` if ``path`` is not in it).""" v = self.get(k) if path is None: return v @@ -264,6 +293,7 @@ def get_config_value(self, k, path=None): @property def rootdir(self): + """The directory the configs are read from.""" return self._prefix def __delitem__(self, k): @@ -285,6 +315,11 @@ def __delitem__(self, k): ) class MyStores(KvStore): + """Store specifications (json files under the user configs directory) that instantiate the store on read. + + A specification is a dict with a ``'$fak'`` entry holding an ``(f, a, k)`` specification, run through ``fakit``. + """ + func_loader = staticmethod(dflt_func_loader) def _obj_of_data(self, data): @@ -299,13 +334,16 @@ def _obj_of_data(self, data): @property def configs(self): + """The underlying store of raw specifications.""" return self.store def without_json_ext(_id): + """Strip the ``.json`` extension from ``_id``.""" assert _id.endswith('.json'), 'Should end with .json' return _id[: -len('.json')] def add_json_ext(k): + """Append ``.json`` to ``k``.""" return k + '.json' ExtLessJsonStore = wrap_kvs( diff --git a/py2store/appendable.py b/py2store/appendable.py index 4fca157..7c8599b 100644 --- a/py2store/appendable.py +++ b/py2store/appendable.py @@ -1,7 +1,9 @@ """Forwards to dol.appendable: -Tools to add append-functionality to key-val stores. The main function is - `appendable_store_cls = add_append_functionality_to_store_cls(store_cls, item2kv, ...)` +Tools to add append-functionality to key-val stores. The main function is:: + + appendable_store_cls = add_append_functionality_to_store_cls(store_cls, item2kv, ...) + You give it the `store_cls` you want to sub class, and a item -> (key, val) function, and you get a store (subclass) that has a `store.append(item)` method. Also includes an extend method (that just called appends in a loop. diff --git a/py2store/examples/kv_walking.py b/py2store/examples/kv_walking.py index 0bc5aee..9b72be7 100644 --- a/py2store/examples/kv_walking.py +++ b/py2store/examples/kv_walking.py @@ -114,13 +114,17 @@ def kv_walk( def conjunction(*funcs, name=None): """Make a function that is the conjunction of other functions. And by that we mean that - ``` - conjunction(*args, **kwargs) - ``` + + .. code-block:: text + + conjunction(*args, **kwargs) + will be equal to - ``` - func_1(*args, **kwargs) & ... & func_n(*args, **kwargs) - ``` + + .. code-block:: text + + func_1(*args, **kwargs) & ... & func_n(*args, **kwargs) + for all `args, kwargs`. """ first_func, *other_funcs = funcs diff --git a/py2store/examples/python_code_stats.py b/py2store/examples/python_code_stats.py index 8290a2d..2ea179c 100644 --- a/py2store/examples/python_code_stats.py +++ b/py2store/examples/python_code_stats.py @@ -1,5 +1,6 @@ """ -Note: Moved to umpyre (pip install umpyre) +Note: + Moved to umpyre (pip install umpyre) Get stats about packages. Your own, or other's. Things like... diff --git a/py2store/ext/gitlab.py b/py2store/ext/gitlab.py index 595be83..a6d4174 100644 --- a/py2store/ext/gitlab.py +++ b/py2store/ext/gitlab.py @@ -1,19 +1,18 @@ """ Stores to talk to gitlab, using requests. -Example: -``` -ogl = GitLabAccessor(base_url="http://...", project_name=None) +For example:: -print(ogl.get_project_names()) # prints all project names -ogl.set_project("PROJECT_NAME") # sets the project to "PROJECT_NAME" -print( - ogl.get_branch_names() -) # gets the branch names of current project (as set previously) -print( - ogl.get_branch("master") -) # gets a json of information about the master branch of current project. -``` + ogl = GitLabAccessor(base_url="http://...", project_name=None) + + print(ogl.get_project_names()) # prints all project names + ogl.set_project("PROJECT_NAME") # sets the project to "PROJECT_NAME" + print( + ogl.get_branch_names() + ) # gets the branch names of current project (as set previously) + print( + ogl.get_branch("master") + ) # gets a json of information about the master branch of current project. """ diff --git a/py2store/ext/matlab.py b/py2store/ext/matlab.py index a1fab01..ff76e44 100644 --- a/py2store/ext/matlab.py +++ b/py2store/ext/matlab.py @@ -8,7 +8,7 @@ from py2store.ext.hdf import HdfFileReader, HdfDatasetReader, HdfRefReader def read_matlab_bytes_with_scipy(b: bytes): - """Note: Doesn't work after matlab 7.3. For >= 7.3, use hdf.""" + """Read MATLAB bytes with scipy; not for MATLAB 7.3 and later (use hdf for those).""" from scipy.io import loadmat return loadmat(BytesIO(b)) diff --git a/py2store/ext/wordnet.py b/py2store/ext/wordnet.py index b2928cf..457ed73 100644 --- a/py2store/ext/wordnet.py +++ b/py2store/ext/wordnet.py @@ -4,19 +4,18 @@ The easiest way to get nltk.corpus.wordnet is -``` -pip install nltk -``` -in your terminal, and then in a python console: -# -# ``` -# import nltk; nltk.download('wordnet') # doctest: +SKIP -# ``` +.. code-block:: text -If you don't like that way, [see here](https://www.nltk.org/install.html) for other ways to get wordnet. + pip install nltk + +in your terminal, and then in a python console:: + + import nltk; nltk.download('wordnet') + +If you don't like that way, `see here `_ for other ways to get wordnet. The central construct of this module is the Synset (a set of synonyms that share a common meaning). -To see a few things you can do with Synsets, naked, [see here](https://www.nltk.org/howto/wordnet.html). +To see a few things you can do with Synsets, naked, `see here `_. Here we put a py2store wrapper around this stuff. diff --git a/py2store/key_mappers/str_utils.py b/py2store/key_mappers/str_utils.py index d37827c..de4c434 100644 --- a/py2store/key_mappers/str_utils.py +++ b/py2store/key_mappers/str_utils.py @@ -103,6 +103,7 @@ def auto_field_format_str(format_str): Returns: A transformed format_str that has no names {inside} {formatting} {braces}. + >>> auto_field_format_str('R/{0}/{one}/{}/{two}/T') 'R/{}/{}/{}/{}/T' """ @@ -117,6 +118,7 @@ def manual_field_format_str(format_str): Returns: A transformed format_str that has no names {inside} {formatting} {braces}. + >>> auto_field_format_str('R/{0}/{one}/{}/{two}/T') 'R/{}/{}/{}/{}/T' """ @@ -146,6 +148,7 @@ def name_fields_in_format_str(format_str, field_names=None): Returns: A transformed format_str + >>> name_fields_in_format_str('R/{0}/{one}/{}/{two}/T') 'R/{0}/{1}/{2}/{3}/T' >>> # Note here that we use the field name to inject a field format as well @@ -175,6 +178,7 @@ def format_params_in_str_format(format_string): Returns: A list of parameter indices used in the format string, in the order they appear, with repetition. Parameter indices could be integers, strings, or None (to denote "automatic field numbering". + >>> format_string = '{0} (no 1) {2}, and {0} is a duplicate, {} is unnamed and {name} is string-named' >>> format_params_in_str_format(format_string) [0, 2, 0, None, 'name'] @@ -194,7 +198,10 @@ def n_format_params_in_str_format(format_string): def is_manual_format_string(format_string): """Says if the format_string uses a manual specification - See Also: is_automatic_format_string and + + See Also: + is_automatic_format_string and + >>> is_manual_format_string('Manual: indices: {1} {2}, named: {named} {fields}') True >>> is_manual_format_string('Auto: only un-indexed and un-named: {} {}...') @@ -209,7 +216,10 @@ def is_manual_format_string(format_string): def is_automatic_format_string(format_string): """Says if the format_string is uses automatic specification - See Also: is_manual_format_params + + See Also: + is_manual_format_params + >>> is_automatic_format_string('Manual: indices: {1} {2}, named: {named} {fields}') False >>> is_automatic_format_string('Auto: only un-indexed and un-named: {} {}...') @@ -224,8 +234,10 @@ def is_automatic_format_string(format_string): def is_hybrid_format_string(format_string): """Says if the format_params is from a hybrid of auto and manual. - Note: Hybrid specifications are considered non-valid and can't be formatted with format_string.format(...). - Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). + + Note: + Hybrid specifications are considered non-valid and can't be formatted with format_string.format(...). + Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). >>> is_hybrid_format_string('Manual: indices: {1} {2}, named: {named} {fields}') False @@ -241,7 +253,9 @@ def is_hybrid_format_string(format_string): def is_manual_format_params(format_params): """Says if the format_params is from a manual specification - See Also: is_automatic_format_params + + See Also: + is_automatic_format_params """ assert not isinstance( format_params, str @@ -251,7 +265,9 @@ def is_manual_format_params(format_params): def is_automatic_format_params(format_params): """Says if the format_params is from an automatic specification - See Also: is_manual_format_params and is_hybrid_format_params + + See Also: + is_manual_format_params and is_hybrid_format_params """ assert not isinstance( format_params, str @@ -261,9 +277,13 @@ def is_automatic_format_params(format_params): def is_hybrid_format_params(format_params): """Says if the format_params is from a hybrid of auto and manual. - Note: Hybrid specifications are considered non-valid and can't be formatted with format_string.format(...). - Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). - See Also: is_manual_format_params and is_automatic_format_params + + Note: + Hybrid specifications are considered non-valid and can't be formatted with format_string.format(...). + Yet, it can be useful for flexibility of expression (but will need to be resolved to be used). + + See Also: + is_manual_format_params and is_automatic_format_params """ assert not isinstance( format_params, str @@ -297,12 +317,14 @@ def empty_arg_and_kwargs_for_format(format_string, fill_val=None): def args_and_kwargs_indices(format_string): """Get the sets of indices and names used in manual specification of format strings, or None, None if auto spec. + Args: format_string: A format string (i.e. a string with {...} to mark parameter placement and formatting Returns: None, None if format_string is an automatic specification set_of_indices_used, set_of_fields_used if it is a manual specification + >>> format_string = '{0} (no 1) {2}, {see} this, {0} is a duplicate (appeared before) and {name} is string-named' >>> assert args_and_kwargs_indices(format_string) == ({0, 2}, {'name', 'see'}) >>> format_string = 'This is a format string with only automatic field specification: {}, {}, {} etc.' diff --git a/py2store/key_mappers/tuples.py b/py2store/key_mappers/tuples.py index 77ced87..c5c87bf 100644 --- a/py2store/key_mappers/tuples.py +++ b/py2store/key_mappers/tuples.py @@ -1,6 +1,7 @@ """ Tools to map tuple-structured keys. That is, converting from any of the following kinds of keys: + * tuples (or list-like) * dicts * formatted/templated strings @@ -34,9 +35,11 @@ def dict_of_tuple(d, fields): def str_of_tuple(d, str_format): """Convert tuple to str. - It's just str_format.format(*d). Why even write such a function? + It's just ``str_format.format(*d)``. Why even write such a function? + (1) To have a consistent interface for key conversions (2) We want a KeyValidationError to occur here + Args: d: tuple if params to str_format str_format: Auto fields format string. If you have manual fields, consider auto_field_format_str to convert. @@ -116,7 +119,7 @@ def mk_obj_of_str(constructor): """Make a function that transforms a string to an object. The factory making inverses of what mk_str_from_obj makes. Args: - constructor: The function (or class) that will be used to make objects from the **kwargs parsed out of the + constructor: The function (or class) that will be used to make objects from the ``**kwargs`` parsed out of the string. Returns: diff --git a/py2store/my/grabbers.py b/py2store/my/grabbers.py index 1ba6cb7..4c4a099 100644 --- a/py2store/my/grabbers.py +++ b/py2store/my/grabbers.py @@ -1,5 +1,15 @@ """ -define stores (and functions) so they give you data as you want it, depending on the extension +Grabbers: fetch an object from a key (a path or URL) and post-process it by kind. + +A grabber is ``py2store.misc.get_obj`` with optional key and value transformations. The +``'ipython'`` grabber turns image, WAV audio and HTML bytes into the matching IPython display +objects, so that grabbing a file in a notebook shows it. + +Main entry points: + +- ``mk_grabber``: build a grabber from ``key_trans`` and ``val_trans`` functions +- ``grabber_for``: the ready-made grabbers by name (``'ipython'``) +- ``ipython_display_val_trans``: the value transformation behind the ``'ipython'`` grabber """ from functools import wraps from io import BytesIO, StringIO @@ -8,6 +18,22 @@ def mk_grabber(*, key_trans=None, val_trans=None): + """Make a function that fetches an object with ``get_obj``, with optional pre- and post-processing. + + Args: + key_trans: Applied to the key before fetching (to strip whitespace or expand a path, say). + val_trans: Applied as ``val_trans(value, key)`` to the fetched object before it is returned. + + Returns: + A function ``grab(k, *args, **kwargs)`` that forwards ``*args`` and ``**kwargs`` to ``get_obj``. + + >>> import os, tempfile + >>> path = os.path.join(tempfile.mkdtemp(), 'hello.txt') + >>> _ = open(path, 'w').write('world') + >>> grab = mk_grabber(key_trans=str.strip, val_trans=lambda v, k: v.upper()) + >>> grab(' ' + path + ' ') + 'WORLD' + """ @wraps(get_obj) def grab(k, *args, **kwargs): """just get_obj, but personalized with pre and/or post processing""" @@ -48,6 +74,10 @@ def _is_html(x, key=None): def ipython_display_val_trans(val, key=None): + """Wrap ``val`` (bytes) in an IPython display object by content: ``Image`` for image data, ``Audio`` for WAV data, ``HTML`` for HTML (by the ``key``'s extension when ``key`` is a string longer than 4 characters, else by a ```` start); anything else is returned unchanged. + + Requires IPython, and uses the ``imghdr`` module to detect images. + """ from IPython.display import Image, Audio, HTML import imghdr @@ -65,6 +95,7 @@ def ipython_display_val_trans(val, key=None): def fullpath(path): + """The absolute path of ``path``, with a leading ``~`` expanded.""" import os return os.path.abspath(os.path.expanduser(path)) @@ -74,6 +105,11 @@ def fullpath(path): def grabber_for(kind): + """The ready-made grabber named ``kind``: ``'ipython'`` gives ``mk_grabber(val_trans=ipython_display_val_trans)``. + + Raises: + ValueError: If ``kind`` is not a known grabber name. + """ if kind == 'ipython': return mk_grabber(val_trans=ipython_display_val_trans) else: diff --git a/py2store/parse_format.py b/py2store/parse_format.py index d2a0502..f9d667a 100644 --- a/py2store/parse_format.py +++ b/py2store/parse_format.py @@ -424,6 +424,7 @@ def with_pattern(pattern, regex_group_count=None): This annotates the type converter with the :attr:`pattern` attribute. EXAMPLE: + >>> @with_pattern(r"\d+") ... def parse_number(text): ... return int(text) @@ -1239,8 +1240,7 @@ def parse( If ``evaluate_result`` is False the return value will be a Match instance with one method: - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True + .evaluate_result() - This will return a Result instance like you would get with ``evaluate_result`` set to True The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. @@ -1280,8 +1280,7 @@ def search( If ``evaluate_result`` is False the return value will be a Match instance with one method: - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True + .evaluate_result() - This will return a Result instance like you would get with ``evaluate_result`` set to True The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. @@ -1320,8 +1319,7 @@ def findall( If ``evaluate_result`` is False each returned value is a Match instance with one method: - .evaluate_result() - This will return a Result instance like you would get - with ``evaluate_result`` set to True + .evaluate_result() - This will return a Result instance like you would get with ``evaluate_result`` set to True The default behaviour is to match strings case insensitively. You may match with case by specifying case_sensitive=True. diff --git a/py2store/persisters/local_files.py b/py2store/persisters/local_files.py index 5d79484..e7fab3b 100644 --- a/py2store/persisters/local_files.py +++ b/py2store/persisters/local_files.py @@ -1,5 +1,22 @@ """ -base classes to work with local files +Base classes and helpers to read and write local files as key-value collections. + +Keys are full file paths. The pieces here (path listing, key validation from a path template, +read, write and delete through ``open``) are what ``py2store.stores.local_store`` assembles +into stores with relative keys. + +Main entry points: + +- ``FileReader``: a directory as a read-only mapping (subdirectories give nested readers, files give ``bytes``) +- ``DirReader``: the subdirectories of a directory, as a mapping +- ``PathFormatPersister``: read, write and delete the files whose paths match a template +- ``iter_filepaths_in_folder_recursively``: the paths of all files under a folder + +>>> import os, tempfile +>>> rootdir = tempfile.mkdtemp() +>>> _ = open(os.path.join(rootdir, 'a.txt'), 'w').write('hi') +>>> [os.path.basename(p) for p in iter_filepaths_in_folder_recursively(rootdir)] +['a.txt'] """ import os import re @@ -25,7 +42,7 @@ class FolderNotFoundError(NoSuchKeyError): - ... + """Raised when writing to a path whose directory does not exist; the message names the first missing directory.""" ######################################################################################################################## @@ -41,6 +58,7 @@ def ensure_slash_suffix(path: str): def pattern_filter(pattern): + """Make a predicate that is true for strings matching the regex ``pattern`` from their start.""" pattern = re.compile(pattern) def _pattern_filter(s): @@ -50,6 +68,7 @@ def _pattern_filter(s): def paths_in_dir_with_slash_suffix_for_dirs(rootdir): + """Yield the full paths directly under ``rootdir``, with a trailing separator on directories.""" for f in iglob(ensure_slash_suffix(rootdir) + '*'): if os.path.isdir(f): yield ensure_slash_suffix(f) @@ -58,11 +77,13 @@ def paths_in_dir_with_slash_suffix_for_dirs(rootdir): def iter_relative_files_and_folder(root_folder): + """The names of the files and folders directly under ``root_folder``.""" root_folder = ensure_slash_suffix(root_folder) return map(lambda x: x.replace(root_folder, ''), iglob(root_folder + '*')) def iter_filepaths_in_folder(root_folder): + """The full paths of the files and folders directly under ``root_folder``.""" return ( os.path.join(root_folder, name) for name in iter_relative_files_and_folder(root_folder) @@ -70,20 +91,30 @@ def iter_filepaths_in_folder(root_folder): def paths_in_dir(rootdir): + """The full paths of the files and folders directly under ``rootdir``.""" return iglob(ensure_slash_suffix(rootdir) + '*') def filepaths_in_dir(rootdir): + """The full paths of the files (not the folders) directly under ``rootdir``.""" return filter(os.path.isfile, iglob(ensure_slash_suffix(rootdir) + '*')) def dirpaths_in_dir(rootdir): + """The full paths of the folders directly under ``rootdir``.""" return filter(os.path.isdir, iglob(ensure_slash_suffix(rootdir) + '*')) def iter_filepaths_in_folder_recursively( root_folder, max_levels=None, _current_level=0 ): + """Yield the full paths of the files under ``root_folder``, recursively. + + Args: + root_folder: The folder to walk. + max_levels: How many folder levels below ``root_folder`` to descend into: ``0`` yields + only the files directly under it, ``None`` means no limit. + """ if max_levels is None: max_levels = inf for full_path in paths_in_dir(root_folder): @@ -98,6 +129,7 @@ def iter_filepaths_in_folder_recursively( def iter_dirpaths_in_folder_recursively(root_folder, max_levels=None, _current_level=0): + """Yield the full paths of the folders under ``root_folder``, recursively (``max_levels`` as in ``iter_filepaths_in_folder_recursively``).""" if max_levels is None: max_levels = inf for full_path in paths_in_dir(root_folder): @@ -123,6 +155,7 @@ def __iter__(self): def __contains__(self, k): """ Check if filepath exists (i.e. the path exists and is a file) + :param k: A key to search for :return: True if k exists, False if not """ @@ -158,6 +191,7 @@ def __iter__(self): def path_match_regex_from_path_format(path_format): + """Compile the regex that full paths matching the ``path_format`` template satisfy (a bare directory matches everything under it).""" if '{' not in path_format: # if the path_format is equal to the _prefix (i.e. there's no {} formatting) # ... append a formatting element so that the matcher can match all subfiles. @@ -167,13 +201,20 @@ def path_match_regex_from_path_format(path_format): class PathFormat: + """Key validation from a path template: which full paths belong to the collection. + + Args: + path_format: The f-string template that the full path keys should match: a root + directory (``'/data/'``, everything under it) or a template such as + ``'/data/{}.csv'`` (only ``.csv`` files under ``/data/``). The directory + containing the part before the first ``{`` is the root, available as ``_prefix``. + + >>> pf = PathFormat('/data/{}.csv') + >>> pf._prefix, pf.is_valid_key('/data/a.csv'), pf.is_valid_key('/data/a.txt') + ('/data/', True, False) + """ + def __init__(self, path_format: str): - """ - A class for pattern-filtered exploration of file paths. - :param path_format: The f-string format that the fullpath keys of the obj source should have. - Often, just the root directory whose FILES contain the (full_filepath, content) data - Also common is to use path_format='{rootdir}/{relative_path}.EXT' to impose a specific extension EXT - """ self._path_format = ( path_format # not intended for use, but keeping in case, for now ) @@ -194,6 +235,7 @@ def _key_filt(k): self._key_filt = _key_filt def is_valid_key(self, k): + """Whether ``k`` matches the path template.""" return self._key_filt(k) @@ -202,6 +244,7 @@ def _is_not_dir(p): def first_non_existing_parent_dir(dirpath): + """The highest ancestor directory of ``dirpath`` that does not exist, or ``''`` if they all exist.""" parent = '' for parent in takewhile(_is_not_dir, Path(dirpath).parents): pass @@ -222,6 +265,7 @@ def w_helpful_folder_not_found_error( extra_msg: str | Callable = '', caught_errors=FileNotFoundError, ): + """Decorator factory: re-raise ``caught_errors`` from a method as ``raise_error``, with the original message plus ``extra_msg`` (a string, or a callable of the method's arguments).""" if isinstance(extra_msg, str): extra_msg_str = extra_msg @@ -326,6 +370,8 @@ class FilepathFormatKeys( PrefixedFilepathsRecursive, IterBasedSizedMixin, ): + """Keys collection of the files matching a path template, recursively under its root (``max_levels`` limits the depth).""" + def __init__(self, path_format: str, max_levels: int = inf): super().__init__(path_format) self._max_levels = max_levels @@ -338,12 +384,24 @@ class DirpathFormatKeys( PrefixedDirpathsRecursive, IterBasedSizedMixin, ): + """Keys collection of the folders matching a path template, recursively under its root (``max_levels`` limits the depth).""" + def __init__(self, path_format: str, max_levels: int = inf): super().__init__(path_format) self._max_levels = max_levels class PathFormatPersister(FilepathFormatKeys, LocalFileRWD): + """Read, write and delete local files whose full paths match a path template. + + Args: + path_format: The path template (see ``PathFormat``). + max_levels: How many folder levels below the root to include when iterating. + mode: ``''``, ``'t'`` or ``'b'``: whether files are opened in text or binary mode. + **open_kwargs: Forwarded to ``open``; ``read_mode`` and ``write_mode`` entries override + the modes derived from ``mode``. + """ + def __init__( self, path_format, max_levels: int = inf, mode=DFLT_OPEN_MODE, **open_kwargs, ): @@ -364,6 +422,7 @@ def __iter__(self): def __contains__(self, k): """ Check if filepath exists (i.e. the path exists and is a file) + :param k: A key to search for :return: True if k exists, False if not """ @@ -395,17 +454,34 @@ def __iter__(self): def extend_prefix(prefix, new_prefix): + """Join ``new_prefix`` to ``prefix``, with a trailing separator.""" return ensure_slash_suffix(os.path.join(prefix, new_prefix)) def endswith_slash(path): + """Whether ``path`` ends with the OS path separator.""" return path.endswith(file_sep) class FileReader(KvReader): """KV Reader whose keys are paths and values are: + - Another FileReader if a path points to a directory - The bytes of the file if the path points to a file. + + Keys are the full paths directly under ``rootdir``; directory keys end with a separator. + + >>> import os, tempfile + >>> rootdir = tempfile.mkdtemp() + >>> _ = open(os.path.join(rootdir, 'a.txt'), 'wb').write(b'hi') + >>> os.mkdir(os.path.join(rootdir, 'sub')) + >>> s = FileReader(rootdir) + >>> sorted(k[len(s.rootdir):] for k in s) + ['a.txt', 'sub/'] + >>> s[os.path.join(rootdir, 'a.txt')] + b'hi' + >>> type(s[os.path.join(rootdir, 'sub', '')]).__name__ + 'FileReader' """ def __init__(self, rootdir): @@ -460,7 +536,20 @@ def __repr__(self): class DirReader(FileReader): - """KV Reader whose keys (AND VALUES) are directory full paths of the subdirectories of rootdir.""" + """KV Reader whose keys are the full paths of the subdirectories of ``rootdir`` and whose values are ``DirReader`` instances of them. + + >>> import os, tempfile + >>> rootdir = tempfile.mkdtemp() + >>> _ = open(os.path.join(rootdir, 'a.txt'), 'wb').write(b'hi') + >>> os.mkdir(os.path.join(rootdir, 'sub')) + >>> s = DirReader(rootdir) + >>> [k[len(s.rootdir):] for k in s] + ['sub/'] + >>> os.path.join(rootdir, 'a.txt') in s + False + >>> type(s[os.path.join(rootdir, 'sub', '')]).__name__ + 'DirReader' + """ def __contains__(self, k): return endswith_slash(k) and super().__contains__(k) diff --git a/py2store/stores/local_store.py b/py2store/stores/local_store.py index c21882f..37c1f3c 100644 --- a/py2store/stores/local_store.py +++ b/py2store/stores/local_store.py @@ -1,5 +1,23 @@ """ -stores to operate on local files +Stores that read and write local files as key-value mappings. + +Keys are paths relative to a root directory and values are the file contents (text, bytes, +or objects through pickle or json serialization). The ``Local*Store`` classes need the +directories to exist already; the ``Quick*Store`` classes create missing directories on write +and pick a temporary root when none is given. + +Main entry points: + +- ``LocalTextStore``, ``LocalBinaryStore``: file contents as ``str`` or ``bytes`` +- ``LocalPickleStore``, ``LocalJsonStore``: values serialized with pickle or json +- ``QuickStore``: ``LocalPickleStore`` with a temporary default root and directories created on write +- ``DirStore``: the subdirectories of a directory, as nested stores + +>>> import tempfile +>>> s = LocalTextStore(tempfile.mkdtemp()) +>>> s['hello.txt'] = 'world' +>>> list(s), s['hello.txt'] +(['hello.txt'], 'world') """ import os from functools import wraps @@ -151,6 +169,8 @@ class PathFormatStore(PathFormatPersister, Persister): class PathFormatStoreWithPrefix(Store): + """``PathFormatStore`` wrapped in a ``Store``, with the root directory available as ``_prefix``.""" + @wraps(PathFormatStore.__init__) def __init__(self, *args, **kwargs): super().__init__(store=PathFormatStore(*args, **kwargs)) @@ -163,25 +183,81 @@ def __init__(self, *args, **kwargs): class RelativePathFormatStore2(PrefixRelativizationMixin, PathFormatStoreWithPrefix): - pass + """``PathFormatStoreWithPrefix`` with keys made relative to the root directory.""" class LocalTextStore(RelativePathFormatStore): - """Local files store for text data""" + """Local files store for text data: keys are paths relative to the root, values are ``str``. + + Directories are not created for you: writing under a missing directory raises + ``FolderNotFoundError``. Use ``QuickTextStore`` to have them created on write. + + Args: + path_format: The root directory, optionally followed by a ``{}`` template (for example + ``'/data/{}.txt'``) that restricts which files under the root are listed + (a key that does not match the template can still be read or written). + max_levels: How many directory levels below the root to include when iterating + (``None`` for no limit). + + >>> import os, tempfile + >>> rootdir = tempfile.mkdtemp() + >>> s = LocalTextStore(rootdir) + >>> len(s) + 0 + >>> s['hello.txt'] = 'world' + >>> list(s), s['hello.txt'], 'hello.txt' in s + (['hello.txt'], 'world', True) + + A template filters the listing; it does not change how a key is written: + + >>> only_txt = LocalTextStore(os.path.join(rootdir, '{}.txt')) + >>> only_txt['notes'] = 'x' # written to rootdir/notes, not rootdir/notes.txt + >>> list(only_txt) + ['hello.txt'] + """ def __init__(self, path_format, max_levels=None): super().__init__(path_format, max_levels=max_levels, mode='t') class LocalBinaryStore(RelativePathFormatStore): - """Local files store for binary data""" + """Local files store for binary data: like ``LocalTextStore``, but values are ``bytes``. + + >>> import tempfile + >>> s = LocalBinaryStore(tempfile.mkdtemp()) + >>> s['raw.bin'] = b'ab' + >>> s['raw.bin'] + b'ab' + """ def __init__(self, path_format, max_levels=None): super().__init__(path_format, max_levels=max_levels, mode='b') class LocalPickleStore(RelativePathFormatStore): - """Local files store with pickle serialization""" + """Local files store with pickle serialization: values are any picklable Python object. + + Args: + path_format: The root directory, optionally with a ``{}`` template (see ``LocalTextStore``). + max_levels: How many directory levels below the root to include when iterating. + fix_imports: Forwarded to ``pickle.dumps`` and ``pickle.loads``. + protocol: The pickle protocol used when writing. + pickle_encoding: Forwarded to ``pickle.loads``. + pickle_errors: Forwarded to ``pickle.loads``. + **open_kwargs: Forwarded to ``open`` when reading and writing files. + + Raises: + ModuleNotFoundError: When unpickling a value needs a module that cannot be imported + (the message names the key). + + >>> import tempfile + >>> s = LocalPickleStore(tempfile.mkdtemp()) + >>> s['obj'] = {'x': [1, 2]} + >>> s['obj'] + {'x': [1, 2]} + >>> s.head() + ('obj', {'x': [1, 2]}) + """ def __init__( self, @@ -200,6 +276,7 @@ def __init__( @classmethod def for_dill(cls, path_format, max_levels=None, open_kwargs=None, *args, **kwargs): + """Make a store that serializes with ``dill`` instead of ``pickle``; ``*args`` and ``**kwargs`` go to ``mk_dill_rw_funcs``.""" from py2store.serializers.pickled import mk_dill_rw_funcs open_kwargs = open_kwargs or {} @@ -221,18 +298,27 @@ def __setitem__(self, k, v): # TODO: hack to take care of problem with head not playing well with wrappers. Find better solution. def head(self): + """Return the first ``(key, value)`` item, or ``None`` if the store is empty.""" for k, v in self.items(): return k, v class LocalJsonStore(SimpleJsonMixin, LocalTextStore): - __doc__ = str(LocalTextStore.__doc__) + SimpleJsonMixin._docsuffix + """Local files store for JSON data: values are read with ``json.loads`` and written with ``json.dumps``. + + >>> import tempfile + >>> s = LocalJsonStore(tempfile.mkdtemp()) + >>> s['conf.json'] = {'a': 1} + >>> s['conf.json'] + {'a': 1} + """ PickleStore = LocalPickleStore # alias def mk_tmp_quick_store_dirpath(dirname=''): + """Path of ``dirname`` under the system temp directory (``tempfile.gettempdir()``).""" from tempfile import gettempdir temp_root = gettempdir() @@ -240,6 +326,7 @@ def mk_tmp_quick_store_dirpath(dirname=''): def mk_absolute_path(path_format): + """Expand a leading ``~`` and make a path starting with ``.`` absolute; other paths are returned unchanged.""" if path_format.startswith('~'): path_format = os.path.expanduser(path_format) elif path_format.startswith('.'): @@ -264,6 +351,7 @@ class AutoMkPathformatMixin: @classmethod def mk_tmp_quick_store_path_format(cls, subpath=''): + """Path of ``subpath`` under the class's folder (``_tmp_dirname``) in the system temp directory.""" return mk_tmp_quick_store_dirpath(os.path.join(cls._tmp_dirname, subpath)) def __init__(self, path_format=None, max_levels=None): @@ -308,19 +396,36 @@ class QuickLocalStoreMixin(AutoMkPathformatMixin, AutoMkDirsOnSetitemMixin): class QuickTextStore(QuickLocalStoreMixin, LocalTextStore): - __doc__ = str(LocalTextStore.__doc__) + QuickLocalStoreMixin._docsuffix + """``LocalTextStore`` with a temporary default root and directories created on write. + + >>> import os, tempfile + >>> s = QuickTextStore(os.path.join(tempfile.mkdtemp(), 'sub')) + >>> s['x/y.txt'] = 'z' # sub/ and sub/x/ are created for you + >>> list(s), s['x/y.txt'] + (['x/y.txt'], 'z') + """ class QuickBinaryStore(QuickLocalStoreMixin, LocalBinaryStore): - __doc__ = str(LocalBinaryStore.__doc__) + QuickLocalStoreMixin._docsuffix + """``LocalBinaryStore`` with a temporary default root and directories created on write.""" class QuickJsonStore(SimpleJsonMixin, QuickTextStore): - __doc__ = str(QuickTextStore.__doc__) + SimpleJsonMixin._docsuffix + """``QuickTextStore`` whose values are read with ``json.loads`` and written with ``json.dumps``.""" class QuickPickleStore(QuickLocalStoreMixin, PickleStore): - __doc__ = str(PickleStore.__doc__) + QuickLocalStoreMixin._docsuffix + """``LocalPickleStore`` with a temporary default root and directories created on write. + + This is what ``QuickStore`` and ``LocalStore`` name. Without a ``path_format`` a folder + under the system temp directory is used, and its path is printed. + + >>> import os, tempfile + >>> s = QuickPickleStore(os.path.join(tempfile.mkdtemp(), 'quick')) + >>> s['deep/er/key'] = [1, 2] + >>> s['deep/er/key'], list(s) + ([1, 2], ['deep/er/key']) + """ QuickStore = QuickPickleStore # alias @@ -355,6 +460,8 @@ def __init__(self, rootdir): class RelativeDirPathFormatKeys(PrefixRelativizationMixin, Store): + """``DirpathFormatKeys`` (the folders under a root) wrapped in a ``Store`` with keys relative to the root.""" + @wraps(DirpathFormatKeys.__init__) def __init__(self, *args, **kwargs): super().__init__(store=DirpathFormatKeys(*args, **kwargs)) diff --git a/py2store/test/util.py b/py2store/test/util.py index f7c2273..7359cc0 100644 --- a/py2store/test/util.py +++ b/py2store/test/util.py @@ -27,17 +27,20 @@ def random_word(length, alphabet, concat_func=add): """Make a random word by concatenating randomly drawn elements from alphabet together + Args: length: Length of the word alphabet: Alphabet to draw from concat_func: The concatenation function (e.g. + for strings and lists) - Note: Repeated elements in alphabet will have more chances of being drawn. + Note: + Repeated elements in alphabet will have more chances of being drawn. Returns: A word (whose type depends on what concatenating elements from alphabet produces). Not making this a proper doctest because I don't know how to seed the global random temporarily + >>> t = random_word(4, 'abcde'); # e.g. 'acae' >>> t = random_word(5, ['a', 'b', 'c']); # e.g. 'cabba' >>> t = random_word(4, [[1, 2, 3], [40, 50], [600], [7000]]); # e.g. [40, 50, 7000, 7000, 1, 2, 3] @@ -66,6 +69,7 @@ def random_string(length=7, alphabet=lower_case_letters): def random_word_gen(word_size_range=(1, 10), alphabet=lower_case_letters, n=100): """Random string generator + Args: word_size_range: An int, 2-tuple of ints, or list-like object that defines the choices of word sizes alphabet: A string or iterable defining the alphabet to draw from diff --git a/py2store/utils/affine_conversion.py b/py2store/utils/affine_conversion.py index 475e43e..ac28d6b 100644 --- a/py2store/utils/affine_conversion.py +++ b/py2store/utils/affine_conversion.py @@ -8,9 +8,11 @@ class AffineConverter: Getting a callable that will perform an affine conversion. Note, it does it as (val - offset) * scale + (Note slope-intercept style (though there is the .from_slope_and_intercept constructor method for that) - Inverse is available through the inv method, performing: + Inverse is available through the inv method, performing:: + val / scale + offset >>> convert = AffineConverter(scale=0.5, offset=1) @@ -49,22 +51,27 @@ def get_affine_converter_and_inverse( scale=1, offset=0, source_type_cast=None, target_type_cast=None ): """ - Getting two affine functions with given scale and offset, that are inverse of each other. Namely (for input val): + Getting two affine functions with given scale and offset, that are inverse of each other. Namely (for input val):: + (val - offset) * scale and val / scale + offset + Note this is not "slope intercept" style!! The source_type_cast and target_type_case (optional), allow the user to specify if these transformations need to be further cast to a given type. + :param scale: :param offset: :param source_type_cast: function to apply to input :param target_type_cast: function to apply to output :return: Two single val functions: affine_converter, inverse_affine_converter - Note: Code is a lot more complex than the basic operations it performs. The reason was a worry of efficiency since - the functions that are returned are intended to be used in long loops. + Note: + Code is a lot more complex than the basic operations it performs. The reason was a worry of efficiency since + the functions that are returned are intended to be used in long loops. - See also: ocore.utils.conversion.AffineConverter + See also: + ocore.utils.conversion.AffineConverter >>> affine_converter, inverse_affine_converter = get_affine_converter_and_inverse(scale=0.5,offset=1) >>> affine_converter(0) diff --git a/py2store/utils/cache_descriptors.py b/py2store/utils/cache_descriptors.py index 2d24576..0a9fc70 100644 --- a/py2store/utils/cache_descriptors.py +++ b/py2store/utils/cache_descriptors.py @@ -68,6 +68,7 @@ def CachedProperty(*args): CachedProperties. This is usable directly as a decorator when given names, or when not. Any of these patterns will work: + * ``@CachedProperty`` * ``@CachedProperty()`` * ``@CachedProperty('n','n2')`` diff --git a/py2store/utils/cumul_aggreg_write.py b/py2store/utils/cumul_aggreg_write.py index dbc7f14..2a979bb 100644 --- a/py2store/utils/cumul_aggreg_write.py +++ b/py2store/utils/cumul_aggreg_write.py @@ -56,11 +56,14 @@ def mk_group_aggregator(item_to_kv, aggregator_op=add, initial=no_initial): (b) group all items according to the key Args: - item_to_kv: - aggregator_op: - initial: + item_to_kv: Function taking an item and returning the ``(key, value)`` pair to group by key. + aggregator_op: The aggregation binary function that is used to aggregate two items together. + The function is used as is by the functools.reduce, applied to the sequence of items that were collected for + a given group + initial: The "empty" element to start the reduce (aggregation) with, if necessary. Returns: + A function taking an iterable of items and yielding ``(key, aggregate)`` pairs, one per key. >>> # Collect words (as a csv string), grouped by the lower case of the first letter >>> ag = mk_group_aggregator(lambda item: (item[0].lower(), item), @@ -103,6 +106,7 @@ def mk_group_aggregator_with_key_func( initial: The "empty" element to start the reduce (aggregation) with, if necessary. Returns: + A function taking an iterable of items and yielding ``(key, aggregate)`` pairs, one per key. >>> # Collect words (as a csv string), grouped by the lower case of the first letter >>> ag = mk_group_aggregator_with_key_func(lambda item: item[0].lower(), diff --git a/py2store/utils/glom.py b/py2store/utils/glom.py index 33b637d..3d3a890 100644 --- a/py2store/utils/glom.py +++ b/py2store/utils/glom.py @@ -40,7 +40,8 @@ Now, at the time of writing this, I've already transformed it to bend it to my liking. At some point it may become something else, but I wanted there to be a trace of what my seed was. Though I can't promise I'll maintain the same functionality as I transform this module, here's -a tutorial on how to use it in it's original form: +a tutorial on how to use it in it's original form:: + https://glom.readthedocs.io/en/latest/ @@ -140,7 +141,8 @@ def __nonzero__(self): def is_iterable(x): """Similar in nature to :func:`callable`, ``is_iterable`` returns - ``True`` if an object is `iterable`_, ``False`` if not. + ``True`` if an object is iterable, ``False`` if not. + >>> is_iterable([]) True >>> is_iterable(1) @@ -1608,9 +1610,10 @@ class Auto: """ Switch to Auto mode (the default) - TODO: this seems like it should be a sub-class of class Spec() -- - if Spec() could help define the interface for new "modes" or dialects - that would also help make match mode feel less duct-taped on + TODO: + this seems like it should be a sub-class of class Spec() -- + if Spec() could help define the interface for new "modes" or dialects + that would also help make match mode feel less duct-taped on """ def __init__(self, spec=None): diff --git a/py2store/utils/mg_selectors.py b/py2store/utils/mg_selectors.py index 5608698..a7cc12a 100644 --- a/py2store/utils/mg_selectors.py +++ b/py2store/utils/mg_selectors.py @@ -142,6 +142,7 @@ class MgDfSelector2(Selector): :param selector: A mongo-like query of the underlying dataframe :return: + >>> _docs = [ ... {'bt': 0, 'tt': 5, 'tag': 'small'}, ... {'bt': 10, 'tt': 15, 'tag': 'small'}, @@ -211,6 +212,7 @@ class LidxSelectorDf(LidxSelector): :param selector: A mongo-like query of the underlying dataframe :return: + >>> _docs = [ ... {'bt': 0, 'tt': 5, 'tag': 'small'}, ... {'bt': 10, 'tt': 15, 'tag': 'small'}, diff --git a/py2store/utils/uri_utils.py b/py2store/utils/uri_utils.py index dbccd85..5dfcd52 100644 --- a/py2store/utils/uri_utils.py +++ b/py2store/utils/uri_utils.py @@ -7,6 +7,7 @@ def parse_uri(uri): """ Parses DB URI string into a dict of params. + :param uri: string formatted as: "scheme://username:password@host:port/database" :return: a dict with these params parsed. """