[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