commit 35fb6f5b28478498f6ce107c5af47dd2aef3ba64 Author: Chris McDonough Date: Thu Mar 11 08:55:25 2010 +0000 Add partial work towards schema / validation / serialization / deserialization thingy. diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..fbf8086 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,7 @@ +Changes +======= + +0.0 (unreleased) +---------------- + +- Initial release. diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt new file mode 100644 index 0000000..4120e5f --- /dev/null +++ b/COPYRIGHT.txt @@ -0,0 +1,3 @@ +Copyright (c) 2010 Agendaless Consulting and Contributors. +(http://www.agendaless.com), All Rights Reserved + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5ced96e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,41 @@ +License + + A copyright notice accompanies this license document that identifies + the copyright holders. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions in source code must retain the accompanying + copyright notice, this list of conditions, and the following + disclaimer. + + 2. Redistributions in binary form must reproduce the accompanying + copyright notice, this list of conditions, and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Names of the copyright holders must not be used to endorse or + promote products derived from this software without prior + written permission from the copyright holders. + + 4. If any files are modified, you must cause the modified files to + carry prominent notices stating that you changed the files and + the date of any change. + + Disclaimer + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND + ANY EXPRESSED 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 + HOLDERS 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. + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..8832c8b --- /dev/null +++ b/README.txt @@ -0,0 +1,12 @@ +cereal +====== + +An extensible package which can be used to: + +- deserialize and validate a data structure composed of strings, + mappings, and lists. + +- serialize an arbitrary data structure to a data structure composed + of strings, mappings, and lists. + +Please see docs/index.rst for further documentation. diff --git a/cereal/__init__.py b/cereal/__init__.py new file mode 100644 index 0000000..7e1c3dd --- /dev/null +++ b/cereal/__init__.py @@ -0,0 +1,371 @@ +import pkg_resources + +def resolve_dotted(dottedname, package=None): + if dottedname.startswith('.') or dottedname.startswith(':'): + if not package: + raise ImportError('name "%s" is irresolveable (no package)' % + dottedname) + if dottedname in ['.', ':']: + dottedname = package.__name__ + else: + dottedname = package.__name__ + dottedname + return pkg_resources.EntryPoint.parse( + 'x=%s' % dottedname).load(False) + +class Invalid(Exception): + + pos = None + parent = None + + def __init__(self, struct, msg=None): + Exception.__init__(self, struct, msg) + self.struct = struct + self.msg = msg + self.subexceptions = [] + + def add(self, error): + if self.msg is not None: + raise ValueError( + 'Exceptions with a message cannot have subexceptions') + error.parent = self + self.subexceptions.append(error) + + def expand(self): + L = [] + L.append(self.msg) + for exc in self.subexceptions: + L.append((exc.pos, self.struct, exc.expand())) + return L + + def pprint(self, indent=0): + istring = ' ' * indent + for exc in self.subexceptions: + if exc.msg: + print '%s%s (%s): %s' % ( + istring, exc.struct.name, exc.pos, exc.msg) + else: + print '%s%s (%s):' % (istring, exc.struct.name, exc.pos) + exc.pprint(indent+2) + + def paths(self): + # thanks chris rossi ;-) + def traverse(node, stack): + stack.append(node) + + if not node.subexceptions: + yield tuple(stack) + + for child in node.subexceptions: + for path in traverse(child, stack): + yield path + + stack.pop() + + return traverse(self, []) + + def asdict(self): + paths = list(self.paths()) + D = {} + for path in paths: + L = [] + msg = None + for exc in path: + msg = exc.msg + if exc.parent is not None: + if isinstance(exc.parent.struct.typ, Positional): + L.append(str(exc.pos)) + else: + L.append(exc.struct.name) + D['.'.join(L)] = msg + return D + +class All(object): + def __init__(self, *validators): + self.validators = validators + + def __call__(self, struct, value): + msgs = [] + for validator in self.validators: + try: + validator(struct, value) + except Invalid, e: + msgs.append(e.msg) + + if msgs: + raise Invalid(struct, msgs) + +class Range(object): + def __init__(self, min=None, max=None): + self.min = min + self.max = max + + def __call__(self, struct, value): + if self.min is not None: + if value < self.min: + raise Invalid( + struct, + '%r is less than minimum value %r' % (value, self.min)) + + if self.max is not None: + if value > self.max: + raise Invalid( + struct, + '%r is greater than maximum value %r' % (value, self.max)) + +class Mapping(object): + def _validate(self, struct, value): + if not isinstance(value, dict): + raise Invalid(struct, '%r is not a mapping type' % value) + return value + + def serialize(self, struct, value): + value = self._validate(struct, value) + result = {} + + error = None + + for num, substruct in enumerate(struct.structs): + name = substruct.name + subval = value.get(name) + try: + if subval is None: + if substruct.required and substruct.default is None: + raise Invalid( + substruct, + '%r is required but empty' % substruct.name) + result[name] = substruct.serialize(struct.default) + else: + result[name] = substruct.serialize(subval) + except Invalid, e: + if error is None: + error = Invalid(substruct) + e.pos = num + error.add(e) + + if error is not None: + raise error + + return result + + def deserialize(self, struct, value): + value = self._validate(struct, value) + + error = None + result = {} + + for num, substruct in enumerate(struct.structs): + name = substruct.name + subval = value.get(name) + + try: + if subval is None: + if substruct.required and substruct.default is None: + raise Invalid( + substruct, + '%r is required but empty' % substruct.name) + result[name] = substruct.default + else: + result[name] = substruct.deserialize(subval) + except Invalid, e: + if error is None: + error = Invalid(struct) + e.pos = num + error.add(e) + + if error is not None: + raise error + + return result + +class Positional(object): + """ + Marker abstract base class meaning 'this type has children which + should be addressed by position instead of name' (e.g. via seq[0], + but never seq['name']). This is consulted by Invalid.asdict when + creating a dictionary representation of an error structure. + """ + +class Tuple(Positional): + """ A type which represents a fixed-length sequence of data + structures, each one of which may be different as denoted by the + types of the associated structure's children.""" + def _validate(self, struct, value): + if not hasattr(value, '__iter__'): + raise Invalid(struct, '%r is not an iterable value' % value) + return list(value) + + def serialize(self, struct, value): + value = self._validate(struct, value) + + error = None + result = [] + + for num, substruct in enumerate(struct.structs): + try: + subval = value[num] + except IndexError: + raise Invalid(struct, 'Wrong number of elements in %r' % value) + try: + result.append(substruct.serialize(subval)) + except Invalid, e: + if error is None: + error = Invalid(struct) + e.pos = num + e.sequence_child = True + error.add(e) + + if error: + raise error + + return tuple(result) + + def deserialize(self, struct, value): + value = self._validate(struct, value) + + error = None + result = [] + + for num, substruct in enumerate(struct.structs): + try: + subval = value[num] + except IndexError: + raise Invalid(struct, 'Wrong number of elements in %r' % value) + try: + result.append(substruct.deserialize(subval)) + except Invalid, e: + if error is None: + error = Invalid(struct) + e.pos = num + e.sequence_child = True + error.add(e) + + if error: + raise error + + return tuple(result) + +class Sequence(Positional): + """ A type which represents a variable-length sequence of values, + all of which must be of the same type as denoted by the type of + ``substruct``""" + def __init__(self, substruct): + self.substruct = substruct + + def _validate(self, struct, value): + if not hasattr(value, '__iter__'): + raise Invalid(struct, '%r is not an iterable value' % value) + return list(value) + + def serialize(self, struct, value): + value = self._validate(struct, value) + + error = None + result = [] + for num, subval in enumerate(value): + try: + result.append(self.substruct.serialize(subval)) + except Invalid, e: + if error is None: + error = Invalid(struct) + e.pos = num + error.add(e) + + if error: + raise error + + return result + + def deserialize(self, struct, value): + value = self._validate(struct, value) + + error = None + result = [] + for num, sub in enumerate(value): + try: + result.append(self.substruct.deserialize(sub)) + except Invalid, e: + if error is None: + error = Invalid(struct) + e.pos = num + error.add(e) + + if error: + raise error + + return result + +class String(object): + """ A type representing a Unicode string """ + def __init__(self, encoding='utf-8'): + self.encoding = encoding + + def _validate(self, struct, value): + try: + if isinstance(value, unicode): + return value + return unicode(value, self.encoding) + except: + raise Invalid(struct, '%r is not a string' % value) + + def serialize(self, struct, value): + decoded = self._validate(struct, value) + return decoded.encode(struct.encoding) + + def deserialize(self, struct, value): + return self._validate(struct, value) + +class Integer(object): + """ A type representing an integer """ + def _validate(self, struct, value): + try: + return int(value) + except: + raise Invalid(struct, '%r is not a number' % value) + + def serialize(self, struct, value): + return str(self._validate(struct, value)) + + def deserialize(self, struct, value): + return self._validate(struct, value) + +class GlobalObject(object): + """ A type representing an importable Python object """ + def __init__(self, package): + self.package = package + + def serialize(self, struct, value): + try: + return value.__name__ + except AttributeError: + raise Invalid(struct, '%r has no __name__' % value) + + def deserialize(self, struct, value): + if not isinstance(value, basestring): + raise Invalid(struct, '%r is not a global object specification') + try: + return resolve_dotted(value, package=self.package) + except ImportError: + raise Invalid(struct, + 'The dotted name %r cannot be imported' % value) + +class Structure(object): + def __init__(self, name, typ, validator=None, default=None, required=True): + self.typ = typ + self.name = name + self.validator = validator + self.default = default + self.required = required + self.structs = [] + + def serialize(self, value): + return self.typ.serialize(self, value) + + def deserialize(self, value): + value = self.typ.deserialize(self, value) + if self.validator is not None: + self.validator(self, value) + return value + + def add(self, struct): + self.structs.append(struct) + diff --git a/cereal/tests.py b/cereal/tests.py new file mode 100644 index 0000000..e888a98 --- /dev/null +++ b/cereal/tests.py @@ -0,0 +1,76 @@ +import unittest + +class TestFunctional(unittest.TestCase): + def _makeSchema(self): + import cereal + + integer = cereal.Structure( + 'int', cereal.Integer(), validator=cereal.Range(0, 10)) + ob = cereal.Structure('ob', cereal.GlobalObject(package=cereal)) + + tup = cereal.Structure('tup', cereal.Tuple()) + tup.add(cereal.Structure('tupint', cereal.Integer())) + tup.add(cereal.Structure('tupstring', cereal.String())) + + seq = cereal.Structure('seq', cereal.Sequence(tup)) + + mapping = cereal.Structure('mapping', cereal.Mapping()) + seq2 = cereal.Structure('seq2', cereal.Sequence(mapping)) + mapping.add(cereal.Structure('key', cereal.Integer())) + mapping.add(cereal.Structure('key2', cereal.Integer())) + + schema = cereal.Structure('', cereal.Mapping()) + + schema.add(integer) + schema.add(ob) + schema.add(tup) + schema.add(seq) + schema.add(seq2) + return schema + + def test_deserialize_ok(self): + import cereal.tests + data = { + 'int':'10', + 'ob':'cereal.tests', + 'tup':('1', 's'), + 'seq':[('1', 's'),('2', 's'), ('3', 's'), ('4', 's')], + 'seq2':[{'key':'1', 'key2':'2'}, {'key':'3', 'key2':'4'}], + } + schema = self._makeSchema() + result = schema.deserialize(data) + self.assertEqual(result['int'], 10) + self.assertEqual(result['ob'], cereal.tests) + self.assertEqual(result['tup'], (1, 's')) + self.assertEqual(result['seq'], + [(1, 's'), (2, 's'), (3, 's'), (4, 's')]) + self.assertEqual(result['seq2'], + [{'key':1, 'key2':2}, {'key':3, 'key2':4}]) + + def test_invalid_asdict(self): + expected = { + 'int': '20 is greater than maximum value 10', + 'ob': "The dotted name 'no.way.this.exists' cannot be imported", + 'seq.0.0': "'q' is not a number", + 'seq.1.0': "'w' is not a number", + 'seq.2.0': "'e' is not a number", + 'seq.3.0': "'r' is not a number", + 'seq2.0.key': "'t' is not a number", + 'seq2.0.key2': "'y' is not a number", + 'seq2.1.key': "'u' is not a number", + 'seq2.1.key2': "'i' is not a number", + 'tup.0': "'s' is not a number"} + import cereal + data = { + 'int':'20', + 'ob':'no.way.this.exists', + 'tup':('s', 's'), + 'seq':[('q', 's'),('w', 's'), ('e', 's'), ('r', 's')], + 'seq2':[{'key':'t', 'key2':'y'}, {'key':'u', 'key2':'i'}], + } + schema = self._makeSchema() + try: + schema.deserialize(data) + except cereal.Invalid, e: + errors = e.asdict() + self.assertEqual(errors, expected) diff --git a/docs/.static/logo_hi.gif b/docs/.static/logo_hi.gif new file mode 100644 index 0000000..178e49f Binary files /dev/null and b/docs/.static/logo_hi.gif differ diff --git a/docs/.static/repoze.css b/docs/.static/repoze.css new file mode 100644 index 0000000..79926b1 --- /dev/null +++ b/docs/.static/repoze.css @@ -0,0 +1,22 @@ +@import url('default.css'); +body { + background-color: #006339; +} + +div.document { + background-color: #dad3bd; +} + +div.sphinxsidebar h3, h4, h5, a { + color: #127c56 !important; +} + +div.related { + color: #dad3bd !important; + background-color: #00744a; +} + +div.related a { + color: #dad3bd !important; +} + diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..e996997 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,70 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html web pickle htmlhelp latex changes linkcheck + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pickle to make pickle files (usable by e.g. sphinx-web)" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview over all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + +clean: + -rm -rf .build/* + +html: + mkdir -p .build/html .build/doctrees + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html + @echo + @echo "Build finished. The HTML pages are in .build/html." + +pickle: + mkdir -p .build/pickle .build/doctrees + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle + @echo + @echo "Build finished; now you can process the pickle files or run" + @echo " sphinx-web .build/pickle" + @echo "to start the sphinx-web server." + +web: pickle + +htmlhelp: + mkdir -p .build/htmlhelp .build/doctrees + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in .build/htmlhelp." + +latex: + mkdir -p .build/latex .build/doctrees + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex + @echo + @echo "Build finished; the LaTeX files are in .build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + mkdir -p .build/changes .build/doctrees + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes + @echo + @echo "The overview file is in .build/changes." + +linkcheck: + mkdir -p .build/linkcheck .build/doctrees + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in .build/linkcheck/output.txt." diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..6f9b395 --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,4 @@ +API Documentation +================= + +XXX diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..25895c9 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# +# cereal documentation build configuration file +# +# This file is execfile()d with the current directory set to its containing +# dir. +# +# The contents of this file are pickled, so don't put values in the +# namespace that aren't pickleable (module imports are okay, they're +# removed automatically). +# +# All configuration values have a default value; values that are commented +# out serve to show the default value. + +import sys, os + +# If your extensions are in another directory, add it here. If the +# directory is relative to the documentation root, use os.path.abspath to +# make it absolute, like shown here. +#sys.path.append(os.path.abspath('some/directory')) + +# 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'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['.templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General substitutions. +project = 'cereal' +copyright = '2010, Repoze Developers ' + +# The default replacements for |version| and |release|, also used in various +# other places throughout the built documents. +# +# The short X.Y version. +version = '0.0' +# The full version, including alpha/beta/rc tags. +release = '0.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 documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directories, that shouldn't be +# searched for source files. +#exclude_dirs = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# Options for HTML output +# ----------------------- + +# The style sheet to use for HTML and HTML Help pages. A file of that name +# must exist either in Sphinx' static/ path, or in one of the custom paths +# given in html_static_path. +html_style = 'repoze.css' + +# The name for this set of Sphinx documents. If None, it defaults to +# " v 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 (within the static path) to place at the top of +# the sidebar. +html_logo = '.static/logo_hi.gif' + +# 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_use_modindex = 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, the reST sources are included in the HTML build as +# _sources/. +#html_copy_source = True + +# If true, an OpenSearch description file will be output, and all pages +# will contain a tag referring to it. The value of this option must +# be the base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'atemplatedoc' + + +# Options for LaTeX output +# ------------------------ + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, document class [howto/manual]). +latex_documents = [ + ('index', 'atemplate.tex', 'cereal Documentation', + 'Repoze Developers', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the +# top of the title page. +latex_logo = '.static/logo_hi.gif' + +# For "manual" documents, if this is true, then toplevel headings are +# parts, not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..0ffd7d1 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,23 @@ +cereal +====== + +Cereal is an extensible package which can be used to: + +- deserialize and validate a data structure composed of strings, + mappings, and lists. + +- serialize an arbitrary data structure to a data structure composed + of strings, mappings, and lists. + +.. toctree:: + :maxdepth: 2 + + api.rst + + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3a3605d --- /dev/null +++ b/setup.cfg @@ -0,0 +1,10 @@ +[easy_install] +zip_ok = false + +[nosetests] +match=^test +where=cereal +nocapture=1 +cover-package=cereal +cover-erase=1 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..df0b38e --- /dev/null +++ b/setup.py @@ -0,0 +1,46 @@ +############################################################################## +# +# Copyright (c) 2010 Agendaless Consulting and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the BSD-like license at +# http://www.repoze.org/LICENSE.txt. A copy of the license 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 +# +############################################################################## + +import os + +from setuptools import setup +from setuptools import find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +README = open(os.path.join(here, 'README.txt')).read() +CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() + +requires = [] + +setup(name='cereal', + version='0.0', + description='A schema-based serialization and deserialization library', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + "Intended Audience :: Developers", + "Programming Language :: Python", + ], + keywords='serialize deserialize validate schema', + author="Agendaless Consulting", + author_email="repoze-dev@lists.repoze.org", + url="http://www.repoze.org", + license="BSD-derived (http://www.repoze.org/LICENSE.txt)", + packages=find_packages(), + include_package_data=True, + zip_safe=False, + tests_require = requires, + install_requires = requires, + test_suite="cereal", + ) +