[Python-modules-commits] [python-jsonref] 01/05: initial import from tag v0.1 of https://github.com/gazpachoking/jsonref into upstream branch

Paolo Greppi paolog-guest at moszumanska.debian.org
Tue Nov 29 07:28:36 UTC 2016


This is an automated email from the git hooks/post-receive script.

paolog-guest pushed a commit to branch master
in repository python-jsonref.

commit c444d61543f67795bf1ae31d4998273d07735703
Author: Paolo Greppi <paolo.greppi at libpf.com>
Date:   Fri Nov 18 12:28:18 2016 +0000

    initial import from tag v0.1 of https://github.com/gazpachoking/jsonref into upstream branch
---
 .travis.yml    |  18 ++
 LICENSE        |  21 +++
 MANIFEST.in    |   3 +
 README.rst     |  66 +++++++
 docs/conf.py   | 235 +++++++++++++++++++++++
 docs/index.rst |  93 +++++++++
 jsonref.py     | 429 ++++++++++++++++++++++++++++++++++++++++++
 proxytypes.py  | 223 ++++++++++++++++++++++
 setup.py       |  39 ++++
 tests.py       | 579 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 1706 insertions(+)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b2c6882
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: python
+python:
+  - "pypy"
+  - "2.6"
+  - "2.7"
+  - "3.2"
+  - "3.3"
+install:
+  - python setup.py -q install
+  - pip install pytest-cov coveralls
+  - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install sphinx; fi
+script:
+  - py.test tests.py --cov jsonref --cov proxytypes --cov-report term-missing
+  # Docs are in python 3 format
+  - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m doctest README.rst; fi
+  - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then sphinx-build -b doctest docs _temp; fi
+after_success:
+  - coveralls
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..163f3da
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (C) 2013 Chase Sterling
+
+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.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..9abb9bd
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include *.rst
+include LICENSE
+include tests.py
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..5f30210
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,66 @@
+jsonref
+=======
+
+.. image:: https://travis-ci.org/gazpachoking/jsonref.png?branch=master
+    :target: https://travis-ci.org/gazpachoking/jsonref
+
+.. image:: https://coveralls.io/repos/gazpachoking/jsonref/badge.png?branch=master
+    :target: https://coveralls.io/r/gazpachoking/jsonref
+
+
+``jsonref`` is a library for automatic dereferencing of
+`JSON Reference <http://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html>`_
+objects for Python (supporting 2.6+ including Python 3).
+
+This library lets you use a data structure with JSON reference objects, as if
+the references had been replaced with the referent data.
+
+
+.. code-block:: python
+
+    >>> from pprint import pprint
+    >>> import jsonref
+
+    >>> # An example json document
+    >>> json_str = """{"real": [1, 2, 3, 4], "ref": {"$ref": "#/real"}}"""
+    >>> data = jsonref.loads(json_str)
+    >>> pprint(data)  # Reference is not evaluated until here
+    {'real': [1, 2, 3, 4], 'ref': [1, 2, 3, 4]}
+
+
+Features
+--------
+
+* References are evaluated lazily. Nothing is dereferenced until it is used.
+
+* Recursive references are supported, and create recursive python data
+  structures.
+
+
+References objects are actually replaced by lazy lookup proxy objects which are
+almost completely transparent.
+
+.. code-block:: python
+
+    >>> data = jsonref.loads('{"real": [1, 2, 3, 4], "ref": {"$ref": "#/real"}}')
+    >>> # You can tell it is a proxy by using the type function
+    >>> type(data["real"]), type(data["ref"])
+    (<class 'list'>, <class 'jsonref.JsonRef'>)
+    >>> # You have direct access to the referent data with the __subject__
+    >>> # attribute
+    >>> type(data["ref"].__subject__)
+    <class 'list'>
+    >>> # If you need to get at the reference object
+    >>> data["ref"].__reference__
+    {'$ref': '#/real'}
+    >>> # Other than that you can use the proxy just like the underlying object
+    >>> ref = data["ref"]
+    >>> isinstance(ref, list)
+    True
+    >>> data["real"] == ref
+    True
+    >>> ref.append(5)
+    >>> del ref[0]
+    >>> # Actions on the reference affect the real data (if it is mutable)
+    >>> pprint(data)
+    {'real': [2, 3, 4, 5], 'ref': [2, 3, 4, 5]}
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..d61a6e7
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+#
+# This file is execfile()d with the current directory set to its containing dir.
+
+from textwrap import dedent
+import sys, os
+
+# 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.
+ext_paths = [os.path.abspath(os.path.pardir), os.path.dirname(__file__)]
+sys.path = ext_paths + sys.path
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# 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',
+    'sphinx.ext.coverage',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.viewcode'
+]
+
+cache_path = "_cache"
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'jsonref'
+copyright = u'2013, Chase Sterling'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# version: The short X.Y version
+# release: The full version, including alpha/beta/rc tags.
+from jsonref import __version__ as release
+version = release.partition("-")[0]
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build', "_cache", "_static", "_templates"]
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+doctest_global_setup = dedent("""
+    from __future__ import print_function
+    from jsonref import *
+""")
+
+intersphinx_mapping = {"python": ("http://docs.python.org/3.3", None)}
+
+
+# -- 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 = 'pyramid'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# 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']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'jsonrefdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'jsonref.tex', u'jsonref Documentation',
+   u'Chase Sterling', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'jsonref', u'jsonref Documentation',
+     [u'Chase Sterling'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'jsonref', u'jsonref Documentation',
+   u'Chase Sterling', 'jsonref', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..69f5b1f
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,93 @@
+=======
+jsonref
+=======
+
+
+.. module:: jsonref
+
+
+``jsonref`` is a library for automatic dereferencing of
+`JSON Reference <http://tools.ietf.org/id/draft-pbryan-zyp-json-ref-03.html>`_
+objects for Python (supporting 2.6+ including Python 3).
+
+.. testcode::
+
+    from pprint import pprint
+    from jsonref import JsonRef
+
+    # Sample JSON data, like from json.load
+    document = {
+        "data": ["a", "b", "c"],
+        "reference": {"$ref": "#/data/1"}
+    }
+
+    # The JsonRef.replace_refs class method will return a copy of the document
+    # with refs replaced by :class:`JsonRef` objects
+    pprint(JsonRef.replace_refs(document))
+
+.. testoutput::
+
+    {'data': ['a', 'b', 'c'], 'reference': 'b'}
+
+
+:class:`JsonRef` Objects
+========================
+
+:class:`JsonRef` objects are used to replace the JSON reference objects within
+the data structure. They act as proxies to whatever data the reference is
+pointing to, but only look up that data the first time they are accessed. Once
+JSON reference objects have been substituted in your data structure, you can
+use the data as if it does not contain references at all.
+
+The primary interface to use :class:`JsonRef` objects is with the class method
+:meth:`JsonRef.replace_refs`. It will return a copy of an object you pass it, with
+all JSON references contained replaced by :class:`JsonRef` objects. There are
+several other options you can pass, seen below.
+
+.. autoclass:: JsonRef(refobj, base_uri=None, loader=None, loader_kwargs=(), jsonschema=False, load_on_repr=True)
+
+    .. automethod:: replace_refs(obj, base_uri=None, loader=None, loader_kwargs=(), jsonschema=False, load_on_repr=True)
+
+    :class:`JsonRef` instances proxy almost all operators and attributes to the
+    referent data, which will be loaded when first accessed. The following
+    attributes are not proxied:
+
+    .. attribute:: __subject__
+
+        Contains the referent data. Accessing this will cause the data to be
+        loaded if it has not already been.
+
+    .. attribute:: __reference__
+
+        Contains the original JSON Reference object. Accessing this attribute
+        will not cause the referent data to be loaded.
+
+
+:mod:`json` module drop in replacement functions
+================================================
+
+Several functions are provided as drop in replacements to functions from the
+:mod:`json` module.
+
+load
+----
+
+:func:`load` and :func:`loads` work just like their
+:mod:`json` counterparts, except for references will already be replaced in the
+return values. If you need to pass in custom parameters to :class:`JsonRef`,
+keyword arguments can be provided by the `ref_kwargs` argument.
+
+.. autofunction:: load
+
+.. autofunction:: loads
+
+dump
+----
+
+:func:`dump` and :func:`dumps` work just like their :mod:`json` counterparts,
+except they output the original reference objects when encountering
+:class:`JsonRef` instances.
+
+.. autofunction:: dump
+
+.. autofunction:: dumps
diff --git a/jsonref.py b/jsonref.py
new file mode 100644
index 0000000..3a6be78
--- /dev/null
+++ b/jsonref.py
@@ -0,0 +1,429 @@
+import functools
+import json
+import operator
+import sys
+import warnings
+
+try:
+    from collections import Mapping, MutableMapping, Sequence
+except ImportError:
+    from collections.abc import Mapping, MutableMapping, Sequence
+
+PY3 = sys.version_info[0] >= 3
+
+if PY3:
+    from urllib import parse as urlparse
+    from urllib.parse import unquote
+    from urllib.request import urlopen
+    unicode = str
+    basestring = str
+    iteritems = operator.methodcaller("items")
+else:
+    import urlparse
+    from urllib import unquote
+    from urllib2 import urlopen
+    iteritems = operator.methodcaller("iteritems")
+
+try:
+    # If requests >=1.0 is available, we will use it
+    import requests
+    if not callable(requests.Response.json):
+        requests = None
+except ImportError:
+    requests = None
+
+from proxytypes import LazyProxy, Proxy
+
+__version__ = "0.1"
+
+
+class JsonRefError(Exception):
+    def __init__(
+            self, message, reference, uri="", base_uri="", path=(), cause=None
+    ):
+        self.message = message
+        self.reference = reference
+        self.uri = uri
+        self.base_uri = base_uri
+        self.path = list(path)
+        self.cause = self.__cause__ = cause
+
+    def __repr__(self):
+        return "<%s: %r>" % (self.__class__.__name__, self.message)
+
+
+class JsonRef(LazyProxy):
+    """
+    A lazy loading proxy to the dereferenced data pointed to by a JSON
+    Reference object.
+
+    """
+
+    __notproxied__ = ("__reference__",)
+
+    @classmethod
+    def replace_refs(cls, obj, _recursive=False, **kwargs):
+        """
+        Returns a deep copy of `obj` with all contained JSON reference objects
+        replaced with :class:`JsonRef` instances.
+
+        :param obj: If this is a JSON reference object, a :class:`JsonRef`
+            instance will be created. If `obj` is not a JSON reference object,
+            a deep copy of it will be created with all contained JSON
+            reference objects replaced by :class:`JsonRef` instances
+        :param base_uri: URI to resolve relative references against
+        :param loader: Callable that takes a URI and returns the parsed JSON
+            (defaults to global ``jsonloader``, a :class:`JsonLoader` instance)
+        :param jsonschema: Flag to turn on `JSON Schema mode
+            <http://json-schema.org/latest/json-schema-core.html#anchor25>`_.
+            'id' keyword changes the `base_uri` for references contained within
+            the object
+        :param load_on_repr: If set to ``False``, :func:`repr` call on a
+            :class:`JsonRef` object will not cause the reference to be loaded
+            if it hasn't already. (defaults to ``True``)
+
+        """
+
+        store = kwargs.setdefault("_store", _URIDict())
+        base_uri, frag = urlparse.urldefrag(kwargs.get("base_uri", ""))
+        store_uri = None  # If this does not get set, we won't store the result
+        if not frag and not _recursive:
+            store_uri = base_uri
+        try:
+            if kwargs.get("jsonschema") and isinstance(obj["id"], basestring):
+                kwargs["base_uri"] = urlparse.urljoin(
+                    kwargs.get("base_uri", ""), obj["id"]
+                )
+                store_uri = kwargs["base_uri"]
+        except (TypeError, LookupError):
+            pass
+
+        try:
+            if not isinstance(obj["$ref"], basestring):
+                raise TypeError
+        except (TypeError, LookupError):
+            pass
+        else:
+            return cls(obj, **kwargs)
+
+        # If our obj was not a json reference object, iterate through it,
+        # replacing children with JsonRefs
+        kwargs["_recursive"] = True
+        path = list(kwargs.pop("_path", ()))
+        if isinstance(obj, Mapping):
+            obj = type(obj)(
+                (k, cls.replace_refs(v, _path=path+[k], **kwargs))
+                for k, v in iteritems(obj)
+            )
+        elif isinstance(obj, Sequence) and not isinstance(obj, basestring):
+            obj = type(obj)(
+                cls.replace_refs(v, _path=path+[i], **kwargs) for i, v in enumerate(obj)
+            )
+        if store_uri is not None:
+            store[store_uri] = obj
+        return obj
+
+    def __init__(
+            self, refobj, base_uri="", loader=None, jsonschema=False,
+            load_on_repr=True, _path=(), _store=None
+    ):
+        if not isinstance(refobj.get("$ref"), basestring):
+            raise ValueError("Not a valid json reference object: %s" % refobj)
+        self.__reference__ = refobj
+        self.base_uri = base_uri
+        self.loader = loader or jsonloader
+        self.jsonschema = jsonschema
+        self.load_on_repr = load_on_repr
+        self.path = list(_path)
+        self.store = _store  # Use the same object to be shared with children
+        if self.store is None:
+            self.store = _URIDict()
+
+    @property
+    def _ref_kwargs(self):
+        return dict(
+            base_uri=self.base_uri, loader=self.loader,
+            jsonschema=self.jsonschema, load_on_repr=self.load_on_repr,
+            _path=self.path, _store=self.store
+        )
+
+    @property
+    def full_uri(self):
+        return urlparse.urljoin(self.base_uri, self.__reference__["$ref"])
+
+    def callback(self):
+        uri, fragment = urlparse.urldefrag(self.full_uri)
+
+        # If we already looked this up, return a reference to the same object
+        if uri in self.store:
+            result = self.resolve_pointer(self.store[uri], fragment)
+        else:
+            # Remote ref
+            try:
+                base_doc = self.loader(uri)
+            except Exception as e:
+                self._error("%s: %s" % (e.__class__.__name__, unicode(e)), cause=e)
+
+            kwargs = self._ref_kwargs
+            kwargs["base_uri"] = uri
+            base_doc = JsonRef.replace_refs(base_doc, **kwargs)
+            result = self.resolve_pointer(base_doc, fragment)
+        if hasattr(result, "__subject__"):
+            # TODO: Circular ref detection
+            result = result.__subject__
+        return result
+
+    def resolve_pointer(self, document, pointer):
+        """
+        Resolve a json pointer ``pointer`` within the referenced ``document``.
+
+        :argument document: the referent document
+        :argument str pointer: a json pointer URI fragment to resolve within it
+
+        """
+        parts = unquote(pointer.lstrip("/")).split("/") if pointer else []
+
+        for part in parts:
+            part = part.replace("~1", "/").replace("~0", "~")
+            if isinstance(document, Sequence):
+                # Try to turn an array index to an int
+                try:
+                    part = int(part)
+                except ValueError:
+                    pass
+            try:
+                document = document[part]
+            except (TypeError, LookupError) as e:
+                self._error("Unresolvable JSON pointer: %r" % pointer, cause=e)
+        return document
+
+    def _error(self, message, cause=None):
+        raise JsonRefError(
+            message,
+            self.__reference__,
+            uri=self.full_uri,
+            base_uri=self.base_uri,
+            path=self.path,
+            cause=cause
+        )
+
+    def __repr__(self):
+        if hasattr(self, "cache") or self.load_on_repr:
+            return repr(self.__subject__)
+        return "JsonRef(%r)" % self.__reference__
+
+
+class _URIDict(MutableMapping):
+    """
+    Dictionary which uses normalized URIs as keys.
+
+    """
+
+    def normalize(self, uri):
+        return urlparse.urlsplit(uri).geturl()
+
+    def __init__(self, *args, **kwargs):
+        self.store = dict()
+        self.store.update(*args, **kwargs)
+
+    def __getitem__(self, uri):
+        return self.store[self.normalize(uri)]
+
+    def __setitem__(self, uri, value):
+        self.store[self.normalize(uri)] = value
+
+    def __delitem__(self, uri):
+        del self.store[self.normalize(uri)]
+
+    def __iter__(self):
+        return iter(self.store)
+
+    def __len__(self):
+        return len(self.store)
+
+    def __repr__(self):
+        return repr(self.store)
+
+
+class JsonLoader(object):
+    """
+    Provides a callable which takes a URI, and returns the loaded JSON referred
+    to by that URI.
+
+    """
+    def __init__(self, store=(), cache_results=True):
+        """
+        :param store: A pre-populated dictionary matching URIs to loaded JSON
+            documents
+        :param cache_results: If this is set to false, the internal cache of
+            loaded JSON documents is not used
+
+        """
+        self.store = _URIDict(store)
+        self.cache_results = cache_results
+
+    def __call__(self, uri, **kwargs):
+        """
+        Return the loaded JSON referred to by `uri`
+
+        :param uri: The URI of the JSON document to load
+        :param **kwargs: Keyword arguments passed to :func:`json.loads`
+
+        """
+        if uri in self.store:
+            return self.store[uri]
+        else:
+            result = self.get_remote_json(uri, **kwargs)
+            if self.cache_results:
+                self.store[uri] = result
+            return result
+
+    def get_remote_json(self, uri, **kwargs):
+        scheme = urlparse.urlsplit(uri).scheme
+
+        if scheme in ["http", "https"] and requests:
+            # Prefer requests, it has better encoding detection
+            try:
+                result = requests.get(uri).json(**kwargs)
+            except TypeError:
+                warnings.warn(
+                    "requests >=1.2 required for custom kwargs to json.loads"
+                )
+                result = requests.get(uri).json()
+        else:
+            # Otherwise, pass off to urllib and assume utf-8
+            result = json.loads(urlopen(uri).read().decode("utf-8"), **kwargs)
+
+        return result
+
+jsonloader = JsonLoader()
+
+
+def load(
+        fp, base_uri="", loader=None, jsonschema=False, load_on_repr=True,
+        **kwargs
+):
+    """
+    Drop in replacement for :func:`json.load`, where JSON references are
+    proxied to their referent data.
+
+    :param fp: File-like object containing JSON document
+    :param **kwargs: This function takes any of the keyword arguments from
+        :meth:`JsonRef.replace_refs`. Any other keyword arguments will be passed to
+        :func:`json.load`
+
+    """
+
+    if loader is None:
+        loader = functools.partial(jsonloader, **kwargs)
+
+    return JsonRef.replace_refs(
+        json.load(fp, **kwargs),
+        base_uri=base_uri,
+        loader=loader,
+        jsonschema=jsonschema,
+        load_on_repr=load_on_repr
+    )
+
+
+def loads(
+        s, base_uri="", loader=None, jsonschema=False, load_on_repr=True,
+        **kwargs
+):
+    """
+    Drop in replacement for :func:`json.loads`, where JSON references are
+    proxied to their referent data.
+
+    :param s: String containing JSON document
+    :param **kwargs: This function takes any of the keyword arguments from
+        :meth:`JsonRef.replace_refs`. Any other keyword arguments will be passed to
+        :func:`json.loads`
+
+    """
+
+    if loader is None:
+        loader = functools.partial(jsonloader, **kwargs)
+
+    return JsonRef.replace_refs(
+        json.loads(s, **kwargs),
+        base_uri=base_uri,
+        loader=loader,
+        jsonschema=jsonschema,
+        load_on_repr=load_on_repr
+    )
+
+
+def load_uri(
+        uri, base_uri=None, loader=None, jsonschema=False, load_on_repr=True
+):
+    """
+    Load JSON data from ``uri`` with JSON references proxied to their referent
+    data.
+
+    :param uri: URI to fetch the JSON from
+    :param **kwargs: This function takes any of the keyword arguments from
+        :meth:`JsonRef.replace_refs`
+
+    """
+
+    if loader is None:
+        loader = jsonloader
+    if base_uri is None:
+        base_uri = uri
+
+    return JsonRef.replace_refs(
+        loader(uri),
+        base_uri=base_uri,
+        loader=loader,
+        jsonschema=jsonschema,
+        load_on_repr=load_on_repr
+    )
+
+
+def dump(obj, fp, **kwargs):
+    """
+    Serialize `obj`, which may contain :class:`JsonRef` objects, as a JSON
+    formatted stream to file-like `fp`. `JsonRef` objects will be dumped as the
+    original reference object they were created from.
+
+    :param obj: Object to serialize
+    :param fp: File-like to output JSON string
+    :param kwargs: Keyword arguments are the same as to :func:`json.dump`
+
+    """
+    # Strangely, json.dumps does not use the custom serialization from our
+    # encoder on python 2.7+. Instead just write json.dumps output to a file.
+    fp.write(dumps(obj, **kwargs))
+
+
+def dumps(obj, **kwargs):
+    """
+    Serialize `obj`, which may contain :class:`JsonRef` objects, to a JSON
+    formatted string. `JsonRef` objects will be dumped as the original
+    reference object they were created from.
+
+    :param obj: Object to serialize
+    :param kwargs: Keyword arguments are the same as to :func:`json.dumps`
+
+    """
+    kwargs["cls"] = _ref_encoder_factory(kwargs.get("cls", json.JSONEncoder))
+    return json.dumps(obj, **kwargs)
+
+
+def _ref_encoder_factory(cls):
+    class JSONRefEncoder(cls):
+        def default(self, o):
+            if hasattr(o, "__reference__"):
+                return o.__reference__
+            return super(JSONRefEncoder, cls).default(o)
+        # Python 2.6 doesn't work with the default method
+        def _iterencode(self, o, *args, **kwargs):
+            if hasattr(o, "__reference__"):
+                o = o.__reference__
+            return super(JSONRefEncoder, self)._iterencode(o, *args, **kwargs)
+        # Pypy doesn't work with either of the other methods
+        def _encode(self, o, *args, **kwargs):
+            if hasattr(o, "__reference__"):
+                o = o.__reference__
+            return super(JSONRefEncoder, self)._encode(o, *args, **kwargs)
+    return JSONRefEncoder
diff --git a/proxytypes.py b/proxytypes.py
new file mode 100644
index 0000000..08a044b
--- /dev/null
+++ b/proxytypes.py
@@ -0,0 +1,223 @@
+"""
+Based on the implementation here by Phillip J. Eby:
+https://pypi.python.org/pypi/ProxyTypes
+"""
+
+from functools import wraps
+import operator
+import sys
+
+
+PY3 = sys.version_info[0] >= 3
+
+OPERATORS = [
+    # Unary
+    "pos", "neg", "abs", "invert",
+    # Comparison
+    "eq", "ne", "lt", "gt", "le", "ge",
+    # Container
+    "getitem", "setitem", "delitem", "contains",
+    # In-place operators
+    "iadd", "isub", "imul", "ifloordiv", "itruediv", "imod", "ipow", "ilshift",
+    "irshift", "iand", "ior", "ixor"
+]
+REFLECTED_OPERATORS = [
+    "add", "sub", "mul", "floordiv", "truediv", "mod", "pow", "and", "or",
+    "xor", "lshift", "rshift"
+]
+# These functions all have magic methods named after them
+MAGIC_FUNCS = [
+    divmod, round, repr, str, hash, len, abs, complex, bool, int, float, iter
+]
+
+if PY3:
+    MAGIC_FUNCS += [bytes]
+    iteritems = operator.methodcaller("items")
+else:
+    OPERATORS += ["getslice", "setslice", "delslice", "idiv"]
+    REFLECTED_OPERATORS += ["div"]
+    MAGIC_FUNCS += [long, unicode, cmp, coerce, oct, hex]
+    iteritems = operator.methodcaller("iteritems")
+
+
+_oga = object.__getattribute__
+_osa = object.__setattr__
+
+
+class ProxyMetaClass(type):
+    def __new__(mcs, name, bases, dct):
+        newcls = super(ProxyMetaClass, mcs).__new__(mcs, name, bases, dct)
+        newcls.__notproxied__ = set(dct.pop("__notproxied__", ()))
+        # Add all the non-proxied attributes from base classes
+        for base in bases:
+            if hasattr(base, "__notproxied__"):
+                newcls.__notproxied__.update(base.__notproxied__)
+        for key, val in iteritems(dct):
+            setattr(newcls, key, val)
+        return newcls
+
+    def __setattr__(cls, attr, value):
+        # Don't do any magic on the methods of the base Proxy class or the
+        # __new__ static method
+        if cls.__bases__[0].__name__ == "_ProxyBase" or attr == "__new__":
+            pass
+        elif callable(value):
+            if getattr(value, "__notproxied__", False):
+                cls.__notproxied__.add(attr)
+            # Don't wrap staticmethods or classmethods
+            if not isinstance(value, (staticmethod, classmethod)):
... 785 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-jsonref.git



More information about the Python-modules-commits mailing list