pax_global_header00006660000000000000000000000064131314054560014514gustar00rootroot0000000000000052 comment=c0fc772abd4857ac5b0649aa8883777ac69b7dc3 pyrlp-0.5.1/000077500000000000000000000000001313140545600126655ustar00rootroot00000000000000pyrlp-0.5.1/.gitignore000066400000000000000000000013331313140545600146550ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ .venv/ build/ develop-eggs/ dist/ downloads/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ docs/rlp.*rst docs/modules.rst # PyBuilder target/ .eggs pyrlp-0.5.1/.travis.yml000066400000000000000000000011231313140545600147730ustar00rootroot00000000000000language: python python: - "3.5" sudo: false env: - TOXENV=py27 - TOXENV=py34 - TOXENV=py35 - TOXENV=pypy #- TOXENV=pypy3 install: - pip install tox - pip install coveralls script: - tox after_success: - coveralls deploy: provider: pypi user: ethereum_pypi_automated password: secure: "aekJDPTyOt3xFI+4nJav0Oc6PlaD6R/rmcCH+Br6G3+L5cI5TWOPmukjPw4CZ/Q6HPPZbKV4B6OYDvEcGrt+9gIWPN44cfk7iGe1MFav1B//H+vEXfUEHpNZuNyhsKkKx5RvDHAbmJZv69kgTsm9JGgv0z+Q5b46Zn6erovsBEs=" on: tags: true repo: ethereum/pyrlp branch: develop distributions: "sdist bdist_wheel" pyrlp-0.5.1/LICENSE000066400000000000000000000021001313140545600136630ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Jnnk, Vitalik Buterin 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. pyrlp-0.5.1/MANIFEST.in000066400000000000000000000003021313140545600144160ustar00rootroot00000000000000include requirements.txt include LICENSE include README.md recursive-include tests * recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-include docs *.rst conf.py Makefile pyrlp-0.5.1/Makefile000066400000000000000000000032201313140545600143220ustar00rootroot00000000000000.PHONY: clean-pyc clean-build docs clean help: @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @echo "clean-pyc - remove Python file artifacts" @echo "clean-test - remove test and coverage artifacts" @echo "lint - check style with flake8" @echo "test - run tests quickly with the default Python" @echo "test-all - run tests on every Python version with tox" @echo "coverage - check code coverage quickly with the default Python" @echo "docs - generate Sphinx HTML documentation, including API docs" @echo "release - package and upload a release" @echo "dist - package" @echo "install - install the package to the active Python's site-packages" clean: clean-build clean-pyc clean-test clean-build: rm -fr build/ rm -fr dist/ rm -fr .eggs/ find . -name '*.egg-info' -exec rm -fr {} + find . -name '*.egg' -exec rm -f {} + clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-test: rm -fr .tox/ rm -f .coverage rm -fr htmlcov/ lint: flake8 rlp tests test: python setup.py test test-all: tox coverage: coverage run --source rlp setup.py test coverage report -m coverage html open htmlcov/index.html docs: rm -f docs/rlp.rst rm -f docs/modules.rst sphinx-apidoc -o docs/ rlp $(MAKE) -C docs clean $(MAKE) -C docs html open docs/_build/html/index.html release: clean python setup.py sdist upload python setup.py bdist_wheel upload dist: clean python setup.py sdist python setup.py bdist_wheel ls -l dist install: clean python setup.py install pyrlp-0.5.1/README.md000066400000000000000000000011261313140545600141440ustar00rootroot00000000000000pyrlp ===== [![Build Status](https://travis-ci.org/ethereum/pyrlp.svg?branch=develop)](https://travis-ci.org/ethereum/pyrlp) [![Coverage Status](https://coveralls.io/repos/ethereum/pyrlp/badge.svg)](https://coveralls.io/r/ethereum/pyrlp) [![PyPI version](https://badge.fury.io/py/rlp.svg)](http://badge.fury.io/py/rlp) A Python implementation of Recursive Length Prefix encoding (RLP). You can find the specification of the standard in the [Ethereum wiki](https://github.com/ethereum/wiki/wiki/RLP) and the documentation of this package on [readthedocs](http://pyrlp.readthedocs.org/en/latest/). pyrlp-0.5.1/docs/000077500000000000000000000000001313140545600136155ustar00rootroot00000000000000pyrlp-0.5.1/docs/Makefile000066400000000000000000000151461313140545600152640ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyrlp.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyrlp.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pyrlp" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyrlp" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." pyrlp-0.5.1/docs/api.rst000066400000000000000000000024471313140545600151270ustar00rootroot00000000000000.. _api-reference: API Reference ============= Functions --------- .. autofunction:: rlp.encode .. autofunction:: rlp.decode .. autofunction:: rlp.decode_lazy .. autoclass:: rlp.LazyList .. autofunction:: rlp.infer_sedes Sedes Objects ------------- .. data:: rlp.sedes.raw A sedes object that does nothing. Thus, it can serialize everything that can be directly encoded in RLP (nested lists of strings). This sedes can be used as a placeholder when deserializing larger structures. .. autoclass:: rlp.sedes.Binary .. automethod:: rlp.sedes.Binary.fixed_length .. data:: rlp.sedes.binary A sedes object for binary data of arbitrary length (an instance of :class:`rlp.sedes.Binary` with default arguments). .. autoclass:: rlp.sedes.BigEndianInt .. data:: rlp.sedes.big_endian_int A sedes object for integers encoded in big endian without any leading zeros (an instance of :class:`rlp.sedes.BigEndianInt` with default arguments). .. autoclass:: rlp.sedes.List .. autoclass:: rlp.sedes.CountableList .. autoclass:: rlp.Serializable :members: Exceptions ---------- .. autoexception:: rlp.RLPException .. autoexception:: rlp.EncodingError .. autoexception:: rlp.DecodingError .. autoexception:: rlp.SerializationError .. autoexception:: rlp.DeserializationError pyrlp-0.5.1/docs/conf.py000066400000000000000000000177441313140545600151310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # pyrlp documentation build configuration file, created by # sphinx-quickstart on Mon Feb 16 14:20:49 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import 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. sys.path.insert(0, os.path.abspath('..')) print 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.viewcode', ] # 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'pyrlp' copyright = u'2015, jnnk' # 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. # # The short X.Y version. version = '0.1-dev' # The full version, including alpha/beta/rc tags. release = '0.1-dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # 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'] # 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' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- 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 = 'default' # 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 # " 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 (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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # 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 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 = 'pyrlpdoc' # -- 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, or own class]). latex_documents = [ ('index', 'pyrlp.tex', u'pyrlp Documentation', u'jnnk', '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', 'pyrlp', u'pyrlp Documentation', [u'jnnk'], 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', 'pyrlp', u'pyrlp Documentation', u'jnnk', 'pyrlp', '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' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False pyrlp-0.5.1/docs/index.rst000066400000000000000000000011571313140545600154620ustar00rootroot00000000000000.. pyrlp documentation master file, created by sphinx-quickstart on Mon Feb 16 14:20:49 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to pyrlp's documentation! ================================= *pyrlp* is a package for encoding and decoding data to and from *recursive length prefix encoding* (*RLP*). This format finds widely spread use in the Ethereum world. .. toctree:: :maxdepth: 2 quickstart tutorial api releases Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyrlp-0.5.1/docs/quickstart.rst000066400000000000000000000014321313140545600165410ustar00rootroot00000000000000Quickstart ========== :: >>> import rlp >>> from rlp.sedes import big_endian_int, text, ListSedes :: >>> rlp.encode(1234) '\x82\x04\xd2' >>> rlp.decode('\x82\x04\xd2', big_endian_int) :: >>> rlp.encode([1, [2, []]]) '\xc4\x01\xc2\x02\xc0' '\xc5\x01\xc3\x02\xc1\x03' >>> list_sedes = ListSedes([big_endian_int, [big_endian_int, []]]) >>> rlp.decode('\xc4\x01\xc2\x02\xc0', list_sedes) [1, [2, []]] :: >>> class Tx(rlp.Serializable): ... fields = [ ... ('from', text), ... ('to', text), ... ('amount', big_endian_int) ... ] ... >>> tx = Tx('me', 'you', 255) >>> rlp.encode(tx) '\xc9\x82me\x83you\x81\xff' >>> rlp.decode('\xc9\x82me\x83you\x81\xff', Tx) == tx True pyrlp-0.5.1/docs/releases.rst000066400000000000000000000003711313140545600161530ustar00rootroot00000000000000Release Notes ============= .. _v0.4.8-release-notes: 0.4.8 ----- - Implement ``Serializable.make_mutable`` and ``rlp.sedes.make_mutable`` API. - Add ``mutable`` flag to ``Serializable.deserialize`` to allow deserialization into mutable objects. pyrlp-0.5.1/docs/tutorial.rst000066400000000000000000000156571313140545600162300ustar00rootroot00000000000000Tutorial ======== Basics ------ There are two types of fundamental items one can encode in RLP: 1) Strings of bytes 2) Lists of other items In this package, byte strings are represented either as Python strings or as ``bytearrays``. Lists can be any sequence, e.g. ``lists`` or ``tuples``. To encode these kinds of objects, use :func:`rlp.encode`:: >>> from rlp import encode >>> encode('ethereum') '\x88ethereum' >>> encode('') '\x80' >>> encode('Lorem ipsum dolor sit amet, consetetur sadipscing elitr.') '\xb88Lorem ipsum dolor sit amet, consetetur sadipscing elitr.' >>> encode([]) '\xc0' >>> encode(['this', ['is', ('a', ('nested', 'list', []))]]) '\xd9\x84this\xd3\x82is\xcfa\xcd\x86nested\x84list\xc0' Decoding is just as simple:: >>> from rlp import decode >>> decode('\x88ethereum') 'ethereum' >>> decode('\x80') '' >>> decode('\xc0') [] >>> decode('\xd9\x84this\xd3\x82is\xcfa\xcd\x86nested\x84list\xc0') ['this', ['is', ['a', ['nested', 'list', []]]]] Now, what if we want to encode a different object, say, an integer? Let's try:: >>> encode(1503) '\x82\x05\xdf' >>> decode('\x82\x05\xdf') '\x05\xdf' Oops, what happened? Encoding worked fine, but :func:`rlp.decode` refused to give an integer back. The reason is that RLP is typeless. It doesn't know if the encoded data represents a number, a string, or a more complicated object. It only distinguishes between byte strings and lists. Therefore, *pyrlp* guesses how to serialize the object into a byte string (here, in big endian notation). When encoded however, the type information is lost and :func:`rlp.decode` returned the result in its most generic form, as a string. Thus, what we need to do is deserialize the result afterwards. Sedes objects ------------- Serialization and its couterpart, deserialization, is done by, what we call, *sedes objects* (borrowing from the word "codec"). For integers, the sedes :mod:`rlp.sedes.big_endian_int` is in charge. To decode our integer, we can pass this sedes to :func:`rlp.decode`:: >>> from rlp.sedes import big_endian_int >>> decode('\x82\x05\xdf', big_endian_int) 1503 For unicode strings, there's the sedes :mod:`rlp.sedes.binary`, which uses UTF-8 to convert to and from byte strings:: >>> from rlp.sedes import binary >>> encode(u'Ðapp') '\x85\xc3\x90app' >>> decode('\x85\xc3\x90app', binary) u'\xd0app' >>> print decode('\x85\xc3\x90app', binary) Ðapp Lists are a bit more difficult as they can contain arbitrarily complex combinations of types. Therefore, we need to create a sedes object specific for each list type. As base class for this we can use :class:`rlp.sedes.List`:: >>> from rlp.sedes import List >>> encode([5, 'fdsa', 0]) '\xc7\x05\x84fdsa\x00' >>> sedes = List([big_endian_int, binary, big_endian_int]) >>> decode('\xc7\x05\x84fdsa\x00', sedes) [5, u'fdsa', 0] Unsurprisingly, it is also possible to nest :class:`rlp.List` objects:: >>> inner = List([binary, binary]) >>> outer = List([inner, inner, inner]) >>> decode(encode(['asdf', 'fdsa']), inner) [u'asdf', u'fdsa'] >>> decode(encode([['a1', 'a2'], ['b1', 'b2'], ['c1', 'c2']]), outer) [[u'a1', u'a2'], [u'b1', u'b2'], [u'c1', u'c2']] What Sedes Objects Actually Are ------------------------------- We saw how to use sedes objects, but what exactly are they? They are characterized by providing the following three member functions: - ``serializable(obj)`` - ``serialize(obj)`` - ``deserialize(serial)`` The latter two are used to convert between a Python object and its representation as byte strings or sequences. The former one may be called by :func:`rlp.encode` to infer which sedes object to use for a given object (see :ref:`inference-section`). For basic types, the sedes object is usually a module (e.g. :mod:`rlp.sedes.big_endian_int` and :mod:`rlp.sedes.binary`). Instances of :class:`rlp.sedes.List` provide the sedes interface too, as well as the class :class:`rlp.Serializable` which is discussed in the following section. Encoding Custom Objects ----------------------- Often, we want to encode our own objects in RLP. Examples from the Ethereum world are transactions, blocks or anything send over the Wire. With *pyrlp*, this is as easy as subclassing :class:`rlp.Serializable`:: >>> import rlp >>> class Transaction(rlp.Serializable) ... fields = ( ... ('sender', binary), ... ('receiver', binary), ... ('amount', big_endian_int) ... ) The class attribute :attr:`~rlp.Serializable.fields` is a sequence of 2-tuples defining the field names and the corresponding sedes. For each name an instance attribute is created, that can conveniently be initialized with :meth:`~rlp.Serializable.__init__`:: >>> tx1 = Transaction('me', 'you', 255) >>> tx2 = Transaction(amount=255, sender='you', receiver='me') >>> tx1.amount 255 At serialization, the field names are dropped and the object is converted to a list, where the provided sedes objects are used to serialize the object attributes:: >>> Transaction.serialize(tx1) ['me', 'you', '\xff'] >>> tx1 == Transaction.deserialize(['me', 'you', '\xff']) True >>> Transaction.serializable(tx1) True As we can see, each subclass of :class:`rlp.Serializable` implements the sedes responsible for its instances. Therefore, we can use :func:`rlp.encode` and :func:`rlp.decode` as expected:: >>> encode(tx1) '\xc9\x82me\x83you\x81\xff' >>> decode('\xc9\x82me\x83you\x81\xff', Transaction) == tx1 True .. _inference-section: Sedes Inference --------------- As we have seen, :func:`rlp.encode` (or, rather, :func:`rlp.infer_sedes`) tries to guess a sedes capable of serializing the object before encoding. In this process, it follows the following steps: 1) Check if the object's class is a sedes object (like every subclass of :class:`rlp.Serializable`). If so, its class is the sedes. 2) Check if one of the entries in :attr:`rlp.sedes.sedes_list` can serialize the object (via ``serializable(obj)``). If so, this is the sedes. 3) Check if the object is a sequence. If so, build a :class:`rlp.sedes.List` by recursively infering a sedes for each of its elements. 4) If none of these steps was successful, sedes inference has failed. If you have build your own basic sedes (e.g. for ``dicts`` or ``floats``), you might want to hook in at step 2 and add it to :attr:`rlp.sedes.sedes_list`, whereby it will be automatically be used by :func:`rlp.encode`. Further Reading --------------- This was basically everything there is to about this package. The technical specification of RLP can be found either in the `Ethereum wiki `_ or in Appendix B of Gavin Woods `Yellow Paper `_. For more detailed information about this package, have a look at the :ref:`API-reference` or the source code. pyrlp-0.5.1/requirements.txt000066400000000000000000000000161313140545600161460ustar00rootroot00000000000000wheel==0.23.0 pyrlp-0.5.1/rlp/000077500000000000000000000000001313140545600134625ustar00rootroot00000000000000pyrlp-0.5.1/rlp/__init__.py000066400000000000000000000006101313140545600155700ustar00rootroot00000000000000from . import sedes from .codec import ( encode, decode, infer_sedes, descend, append, pop, compare_length, insert, ) from .exceptions import ( RLPException, EncodingError, DecodingError, SerializationError, DeserializationError, ) from .lazy import decode_lazy, peek, LazyList from .sedes import Serializable, make_immutable, make_mutable pyrlp-0.5.1/rlp/codec.py000066400000000000000000000273671313140545600151300ustar00rootroot00000000000000import collections import sys from .exceptions import EncodingError, DecodingError from .utils import (Atomic, str_to_bytes, is_integer, ascii_chr, safe_ord, big_endian_to_int, int_to_big_endian) from .sedes.binary import Binary as BinaryClass from .sedes import big_endian_int, binary from .sedes.lists import List, Serializable, is_sedes if sys.version_info.major == 2: from itertools import imap as map def encode(obj, sedes=None, infer_serializer=True, cache=False): """Encode a Python object in RLP format. By default, the object is serialized in a suitable way first (using :func:`rlp.infer_sedes`) and then encoded. Serialization can be explicitly suppressed by setting `infer_serializer` to ``False`` and not passing an alternative as `sedes`. If `obj` has an attribute :attr:`_cached_rlp` (as, notably, :class:`rlp.Serializable`) and its value is not `None`, this value is returned bypassing serialization and encoding, unless `sedes` is given (as the cache is assumed to refer to the standard serialization which can be replaced by specifying `sedes`). If `obj` is a :class:`rlp.Serializable` and `cache` is true, the result of the encoding will be stored in :attr:`_cached_rlp` if it is empty and :meth:`rlp.Serializable.make_immutable` will be invoked on `obj`. :param sedes: an object implementing a function ``serialize(obj)`` which will be used to serialize ``obj`` before encoding, or ``None`` to use the infered one (if any) :param infer_serializer: if ``True`` an appropriate serializer will be selected using :func:`rlp.infer_sedes` to serialize `obj` before encoding :param cache: cache the return value in `obj._cached_rlp` if possible and make `obj` immutable (default `False`) :returns: the RLP encoded item :raises: :exc:`rlp.EncodingError` in the rather unlikely case that the item is too big to encode (will not happen) :raises: :exc:`rlp.SerializationError` if the serialization fails """ if isinstance(obj, Serializable): if obj._cached_rlp and sedes is None: return obj._cached_rlp else: really_cache = cache if sedes is None else False else: really_cache = False if sedes: item = sedes.serialize(obj) elif infer_serializer: item = infer_sedes(obj).serialize(obj) else: item = obj result = encode_raw(item) if really_cache: obj._cached_rlp = result obj.make_immutable() return result class RLPData(str): "wraper to mark already rlp serialized data" pass def encode_raw(item): """RLP encode (a nested sequence of) :class:`Atomic`s.""" if isinstance(item, RLPData): return item elif isinstance(item, Atomic): if len(item) == 1 and safe_ord(item[0]) < 128: return str_to_bytes(item) payload = str_to_bytes(item) prefix_offset = 128 # string elif isinstance(item, collections.Sequence): payload = b''.join(encode_raw(x) for x in item) prefix_offset = 192 # list else: msg = 'Cannot encode object of type {0}'.format(type(item).__name__) raise EncodingError(msg, item) try: prefix = length_prefix(len(payload), prefix_offset) except ValueError: raise EncodingError('Item too big to encode', item) return prefix + payload def length_prefix(length, offset): """Construct the prefix to lists or strings denoting their length. :param length: the length of the item in bytes :param offset: ``0x80`` when encoding raw bytes, ``0xc0`` when encoding a list """ if length < 56: return ascii_chr(offset + length) elif length < 256**8: length_string = int_to_big_endian(length) return ascii_chr(offset + 56 - 1 + len(length_string)) + length_string else: raise ValueError('Length greater than 256**8') def consume_length_prefix(rlp, start): """Read a length prefix from an RLP string. :param rlp: the rlp string to read from :param start: the position at which to start reading :returns: a tuple ``(type, length, end)``, where ``type`` is either ``str`` or ``list`` depending on the type of the following payload, ``length`` is the length of the payload in bytes, and ``end`` is the position of the first payload byte in the rlp string """ b0 = safe_ord(rlp[start]) if b0 < 128: # single byte return (str, 1, start) elif b0 < 128 + 56: # short string if b0 - 128 == 1 and safe_ord(rlp[start + 1]) < 128: raise DecodingError('Encoded as short string although single byte was possible', rlp) return (str, b0 - 128, start + 1) elif b0 < 192: # long string ll = b0 - 128 - 56 + 1 if rlp[start + 1:start + 2] == b'\x00': raise DecodingError('Length starts with zero bytes', rlp) l = big_endian_to_int(rlp[start + 1:start + 1 + ll]) if l < 56: raise DecodingError('Long string prefix used for short string', rlp) return (str, l, start + 1 + ll) elif b0 < 192 + 56: # short list return (list, b0 - 192, start + 1) else: # long list ll = b0 - 192 - 56 + 1 if rlp[start + 1:start + 2] == b'\x00': raise DecodingError('Length starts with zero bytes', rlp) l = big_endian_to_int(rlp[start + 1:start + 1 + ll]) if l < 56: raise DecodingError('Long list prefix used for short list', rlp) return (list, l, start + 1 + ll) def consume_payload(rlp, start, type_, length): """Read the payload of an item from an RLP string. :param rlp: the rlp string to read from :param type_: the type of the payload (``str`` or ``list``) :param start: the position at which to start reading :param length: the length of the payload in bytes :returns: a tuple ``(item, end)``, where ``item`` is the read item and ``end`` is the position of the first unprocessed byte """ if type_ == str: return (rlp[start:start + length], start + length) elif type_ == list: items = [] next_item_start = start end = next_item_start + length while next_item_start < end: # item, next_item_start = consume_item(rlp, next_item_start) t, l, s = consume_length_prefix(rlp, next_item_start) item, next_item_start = consume_payload(rlp, s, t, l) items.append(item) if next_item_start > end: raise DecodingError('List length prefix announced a too small ' 'length', rlp) return (items, next_item_start) else: raise TypeError('Type must be either list or str') def consume_item(rlp, start): """Read an item from an RLP string. :param rlp: the rlp string to read from :param start: the position at which to start reading :returns: a tuple ``(item, end)`` where ``item`` is the read item and ``end`` is the position of the first unprocessed byte """ t, l, s = consume_length_prefix(rlp, start) return consume_payload(rlp, s, t, l) def decode(rlp, sedes=None, strict=True, **kwargs): """Decode an RLP encoded object. If the deserialized result `obj` has an attribute :attr:`_cached_rlp` (e.g. if `sedes` is a subclass of :class:`rlp.Serializable`) it will be set to `rlp`, which will improve performance on subsequent :func:`rlp.encode` calls. Bear in mind however that `obj` needs to make sure that this value is updated whenever one of its fields changes or prevent such changes entirely (:class:`rlp.sedes.Serializable` does the latter). :param sedes: an object implementing a function ``deserialize(code)`` which will be applied after decoding, or ``None`` if no deserialization should be performed :param \*\*kwargs: additional keyword arguments that will be passed to the deserializer :param strict: if false inputs that are longer than necessary don't cause an exception :returns: the decoded and maybe deserialized Python object :raises: :exc:`rlp.DecodingError` if the input string does not end after the root item and `strict` is true :raises: :exc:`rlp.DeserializationError` if the deserialization fails """ rlp = str_to_bytes(rlp) try: item, end = consume_item(rlp, 0) except IndexError: raise DecodingError('RLP string to short', rlp) if end != len(rlp) and strict: msg = 'RLP string ends with {} superfluous bytes'.format(len(rlp) - end) raise DecodingError(msg, rlp) if sedes: obj = sedes.deserialize(item, **kwargs) if hasattr(obj, '_cached_rlp'): obj._cached_rlp = rlp assert not isinstance(obj, Serializable) or not obj.is_mutable() return obj else: return item def descend(rlp, *path): rlp = str_to_bytes(rlp) for p in path: pos = 0 _typ, _len, pos = consume_length_prefix(rlp, pos) if _typ != list: raise DecodingError('Trying to descend through a non-list!', rlp) for i in range(p): _, _l, _p = consume_length_prefix(rlp, pos) pos = _l + _p _, _l, _p = consume_length_prefix(rlp, pos) rlp = rlp[pos: _p + _l] return rlp def infer_sedes(obj): """Try to find a sedes objects suitable for a given Python object. The sedes objects considered are `obj`'s class, `big_endian_int` and `binary`. If `obj` is a sequence, a :class:`rlp.sedes.List` will be constructed recursively. :param obj: the python object for which to find a sedes object :raises: :exc:`TypeError` if no appropriate sedes could be found """ if is_sedes(obj.__class__): return obj.__class__ if is_integer(obj) and obj >= 0: return big_endian_int if BinaryClass.is_valid_type(obj): return binary if isinstance(obj, collections.Sequence): return List(map(infer_sedes, obj)) msg = 'Did not find sedes handling type {}'.format(type(obj).__name__) raise TypeError(msg) def append(rlpdata, obj): _typ, _len, _pos = consume_length_prefix(rlpdata, 0) assert _typ is list rlpdata = rlpdata[_pos:] + encode(obj) prefix = length_prefix(len(rlpdata), 192) return prefix + rlpdata def insert(rlpdata, index, obj): _typ, _len, _pos = consume_length_prefix(rlpdata, 0) _beginpos = _pos assert _typ is list for i in range(index): _, _l, _p = consume_length_prefix(rlpdata, _pos) _pos = _l + _p if _l + _p >= len(rlpdata): break rlpdata = rlpdata[_beginpos:_pos] + encode(obj) + rlpdata[_pos:] prefix = length_prefix(len(rlpdata), 192) return prefix + rlpdata def pop(rlpdata, index=2**50): _typ, _len, _pos = consume_length_prefix(rlpdata, 0) _initpos = _pos assert _typ is list while index > 0: _, _l, _p = consume_length_prefix(rlpdata, _pos) if _l + _p >= len(rlpdata): break _pos = _l + _p index -= 1 _, _l, _p = consume_length_prefix(rlpdata, _pos) newdata = rlpdata[_initpos:_pos] + rlpdata[_l + _p:] prefix = length_prefix(len(newdata), 192) return prefix + newdata EMPTYLIST = encode([]) def compare_length(rlpdata, length): _typ, _len, _pos = consume_length_prefix(rlpdata, 0) _initpos = _pos assert _typ is list lenlist = 0 if rlpdata == EMPTYLIST: return -1 if length > 0 else 1 if length < 0 else 0 while 1: if lenlist > length: return 1 _, _l, _p = consume_length_prefix(rlpdata, _pos) lenlist += 1 if _l + _p >= len(rlpdata): break _pos = _l + _p return 0 if lenlist == length else -1 pyrlp-0.5.1/rlp/exceptions.py000066400000000000000000000132611313140545600162200ustar00rootroot00000000000000class RLPException(Exception): """Base class for exceptions raised by this package.""" pass class EncodingError(RLPException): """Exception raised if encoding fails. :ivar obj: the object that could not be encoded """ def __init__(self, message, obj): super(EncodingError, self).__init__(message) self.obj = obj class DecodingError(RLPException): """Exception raised if decoding fails. :ivar rlp: the RLP string that could not be decoded """ def __init__(self, message, rlp): super(DecodingError, self).__init__(message) self.rlp = rlp class SerializationError(RLPException): """Exception raised if serialization fails. :ivar obj: the object that could not be serialized """ def __init__(self, message, obj): super(SerializationError, self).__init__(message) self.obj = obj class ListSerializationError(SerializationError): """Exception raised if serialization by a :class:`sedes.List` fails. :ivar element_exception: the exception that occurred during the serialization of one of the elements, or `None` if the error is unrelated to a specific element :ivar index: the index in the list that produced the error or `None` if the error is unrelated to a specific element """ def __init__(self, message=None, obj=None, element_exception=None, index=None): if message is None: assert index is not None assert element_exception is not None message = ('Serialization failed because of element at index {} ' '("{}")'.format(index, str(element_exception))) super(ListSerializationError, self).__init__(message, obj) self.index = index self.element_exception = element_exception class ObjectSerializationError(SerializationError): """Exception raised if serialization of a :class:`sedes.Serializable` object fails. :ivar sedes: the :class:`sedes.Serializable` that failed :ivar list_exception: exception raised by the underlying list sedes, or `None` if no such exception has been raised :ivar field: name of the field of the object that produced the error, or `None` if no field responsible for the error """ def __init__(self, message=None, obj=None, sedes=None, list_exception=None): if message is None: assert list_exception is not None if list_exception.element_exception is None: field = None message = ('Serialization failed because of underlying list ' '("{}")'.format(str(list_exception))) else: assert sedes is not None field = sedes.fields[list_exception.index][0] message = ('Serialization failed because of field {} ' '("{}")'.format(field, str(list_exception.element_exception))) else: field = None super(ObjectSerializationError, self).__init__(message, obj) self.field = field self.list_exception = list_exception class DeserializationError(RLPException): """Exception raised if deserialization fails. :ivar serial: the decoded RLP string that could not be deserialized """ def __init__(self, message, serial): super(DeserializationError, self).__init__(message) self.serial = serial class ListDeserializationError(DeserializationError): """Exception raised if deserialization by a :class:`sedes.List` fails. :ivar element_exception: the exception that occurred during the deserialization of one of the elements, or `None` if the error is unrelated to a specific element :ivar index: the index in the list that produced the error or `None` if the error is unrelated to a specific element """ def __init__(self, message=None, serial=None, element_exception=None, index=None): if not message: assert index is not None assert element_exception is not None message = ('Deserialization failed because of element at index {} ' '("{}")'.format(index, str(element_exception))) super(ListDeserializationError, self).__init__(message, serial) self.index = index self.element_exception = element_exception class ObjectDeserializationError(DeserializationError): """Exception raised if deserialization by a :class:`sedes.Serializable` fails. :ivar sedes: the :class:`sedes.Serializable` that failed :ivar list_exception: exception raised by the underlying list sedes, or `None` if no such exception has been raised :ivar field: name of the field of the object that produced the error, or `None` if no field responsible for the error """ def __init__(self, message=None, serial=None, sedes=None, list_exception=None): if not message: assert list_exception is not None if list_exception.element_exception is None: field = None message = ('Deserialization failed because of underlying list ' '("{}")'.format(str(list_exception))) else: assert sedes is not None field = sedes.fields[list_exception.index][0] message = ('Deserialization failed because of field {} ' '("{}")'.format(field, str(list_exception.element_exception))) super(ObjectDeserializationError, self).__init__(message, serial) self.sedes = sedes self.list_exception = list_exception self.field = field pyrlp-0.5.1/rlp/lazy.py000066400000000000000000000127211313140545600150160ustar00rootroot00000000000000from collections import Iterable, Sequence from .codec import consume_length_prefix, consume_payload from .exceptions import DecodingError from .utils import Atomic def decode_lazy(rlp, sedes=None, **sedes_kwargs): """Decode an RLP encoded object in a lazy fashion. If the encoded object is a bytestring, this function acts similar to :func:`rlp.decode`. If it is a list however, a :class:`LazyList` is returned instead. This object will decode the string lazily, avoiding both horizontal and vertical traversing as much as possible. The way `sedes` is applied depends on the decoded object: If it is a string `sedes` deserializes it as a whole; if it is a list, each element is deserialized individually. In both cases, `sedes_kwargs` are passed on. Note that, if a deserializer is used, only "horizontal" but not "vertical lazyness" can be preserved. :param rlp: the RLP string to decode :param sedes: an object implementing a method ``deserialize(code)`` which is used as described above, or ``None`` if no deserialization should be performed :param \*\*sedes_kwargs: additional keyword arguments that will be passed to the deserializers :returns: either the already decoded and deserialized object (if encoded as a string) or an instance of :class:`rlp.LazyList` """ item, end = consume_item_lazy(rlp, 0) if end != len(rlp): raise DecodingError('RLP length prefix announced wrong length', rlp) if isinstance(item, LazyList): item.sedes = sedes item.sedes_kwargs = sedes_kwargs return item elif sedes: return sedes.deserialize(item, **sedes_kwargs) else: return item def consume_item_lazy(rlp, start): """Read an item from an RLP string lazily. If the length prefix announces a string, the string is read; if it announces a list, a :class:`LazyList` is created. :param rlp: the rlp string to read from :param start: the position at which to start reading :returns: a tuple ``(item, end)`` where ``item`` is the read string or a :class:`LazyList` and ``end`` is the position of the first unprocessed byte. """ t, l, s = consume_length_prefix(rlp, start) if t == str: #item, _ = consume_payload(rlp, s, str, l), s + l return consume_payload(rlp, s, str, l) else: assert t == list return LazyList(rlp, s, s + l), s + l class LazyList(Sequence): """A RLP encoded list which decodes itself when necessary. Both indexing with positive indices and iterating are supported. Getting the length with :func:`len` is possible as well but requires full horizontal encoding. :param rlp: the rlp string in which the list is encoded :param start: the position of the first payload byte of the encoded list :param end: the position of the last payload byte of the encoded list :param sedes: a sedes object which deserializes each element of the list, or ``None`` for no deserialization :param \*\*sedes_kwargs: keyword arguments which will be passed on to the deserializer """ def __init__(self, rlp, start, end, sedes=None, **sedes_kwargs): self.rlp = rlp self.start = start self.end = end self.index = start self._elements = [] self._len = None self.sedes = sedes self.sedes_kwargs = sedes_kwargs def next(self): if self.index == self.end: self._len = len(self._elements) raise StopIteration assert self.index < self.end item, end = consume_item_lazy(self.rlp, self.index) self.index = end if self.sedes: item = self.sedes.deserialize(item, **self.sedes_kwargs) self._elements.append(item) return item def __getitem__(self, i): try: while len(self._elements) <= i: self.next() except StopIteration: assert self.index == self.end raise IndexError('Index %d out of range' % i) return self._elements[i] def __len__(self): if not self._len: try: while True: self.next() except StopIteration: self._len = len(self._elements) return self._len def peek(rlp, index, sedes=None): """Get a specific element from an rlp encoded nested list. This function uses :func:`rlp.decode_lazy` and, thus, decodes only the necessary parts of the string. Usage example:: >>> rlpdata = rlp.encode([1, 2, [3, [4, 5]]]) >>> rlp.peek(rlpdata, 0, rlp.sedes.big_endian_int) 1 >>> rlp.peek(rlpdata, [2, 0], rlp.sedes.big_endian_int) 3 :param rlp: the rlp string :param index: the index of the element to peek at (can be a list for nested data) :param sedes: a sedes used to deserialize the peeked at object, or `None` if no deserialization should be performed :raises: :exc:`IndexError` if `index` is invalid (out of range or too many levels) """ ll = decode_lazy(rlp) if not isinstance(index, Iterable): index = [index] for i in index: if isinstance(ll, Atomic): raise IndexError('Too many indices given') ll = ll[i] if sedes: return sedes.deserialize(ll) else: return ll pyrlp-0.5.1/rlp/sedes/000077500000000000000000000000001313140545600145655ustar00rootroot00000000000000pyrlp-0.5.1/rlp/sedes/__init__.py000066400000000000000000000003011313140545600166700ustar00rootroot00000000000000from . import raw from .binary import Binary, binary from .big_endian_int import BigEndianInt, big_endian_int from .lists import CountableList, List, Serializable, make_immutable, make_mutable pyrlp-0.5.1/rlp/sedes/big_endian_int.py000066400000000000000000000030721313140545600200720ustar00rootroot00000000000000from ..exceptions import DeserializationError, SerializationError from ..utils import int_to_big_endian, big_endian_to_int, is_integer, ascii_chr class BigEndianInt(object): """A sedes for big endian integers. :param l: the size of the serialized representation in bytes or `None` to use the shortest possible one """ def __init__(self, l=None): self.l = l def serialize(self, obj): if not is_integer(obj): raise SerializationError('Can only serialize integers', obj) if self.l is not None and obj >= 256**self.l: raise SerializationError('Integer too large (does not fit in {} ' 'bytes)'.format(self.l), obj) if obj < 0: raise SerializationError('Cannot serialize negative integers', obj) if obj == 0: s = b'' else: s = int_to_big_endian(obj) if self.l is not None: return b'\x00' * max(0, self.l - len(s)) + s else: return s def deserialize(self, serial): if self.l is not None and len(serial) != self.l: raise DeserializationError('Invalid serialization (wrong size)', serial) if self.l is None and len(serial) > 0 and serial[0:1] == ascii_chr(0): raise DeserializationError('Invalid serialization (not minimal ' 'length)', serial) serial = serial or b'\x00' return big_endian_to_int(serial) big_endian_int = BigEndianInt() pyrlp-0.5.1/rlp/sedes/binary.py000066400000000000000000000041411313140545600164230ustar00rootroot00000000000000import sys from ..exceptions import SerializationError, DeserializationError from ..utils import Atomic, str_to_bytes, bytes_to_str class Binary(object): """A sedes object for binary data of certain length. :param min_length: the minimal length in bytes or `None` for no lower limit :param max_length: the maximal length in bytes or `None` for no upper limit :param allow_empty: if true, empty strings are considered valid even if a minimum length is required otherwise """ def __init__(self, min_length=None, max_length=None, allow_empty=False): self.min_length = min_length or 0 self.max_length = max_length or float('inf') self.allow_empty = allow_empty @classmethod def fixed_length(cls, l, allow_empty=False): """Create a sedes for binary data with exactly `l` bytes.""" return cls(l, l, allow_empty=allow_empty) @classmethod def is_valid_type(cls, obj): if sys.version_info.major == 2: return isinstance(obj, (str, unicode, bytearray)) else: return isinstance(obj, (str, bytes)) def is_valid_length(self, l): return any((self.min_length <= l <= self.max_length, self.allow_empty and l == 0)) def serialize(self, obj): if not Binary.is_valid_type(obj): raise SerializationError('Object is not a serializable ({})'.format(type(obj)), obj) if isinstance(obj, (bytes, bytearray)): serial = obj else: serial = str_to_bytes(obj) if not self.is_valid_length(len(serial)): raise SerializationError('Object has invalid length', serial) return serial def deserialize(self, serial): if not isinstance(serial, Atomic): m = 'Objects of type {} cannot be deserialized' raise DeserializationError(m.format(type(serial).__name__), serial) if self.is_valid_length(len(serial)): return serial else: raise DeserializationError('{} has invalid length'.format(type(serial)), serial) binary = Binary() pyrlp-0.5.1/rlp/sedes/lists.py000066400000000000000000000273311313140545600163030ustar00rootroot00000000000000"""Module for sedes objects that use lists as serialization format.""" import sys from collections import Sequence from itertools import count from ..exceptions import (SerializationError, ListSerializationError, ObjectSerializationError, DeserializationError, ListDeserializationError, ObjectDeserializationError) from ..sedes.binary import Binary as BinaryClass if sys.version_info.major == 2: from itertools import izip as zip def is_sedes(obj): """Check if `obj` is a sedes object. A sedes object is characterized by having the methods `serialize(obj)` and `deserialize(serial)`. """ # return all(hasattr(obj, m) for m in ('serialize', 'deserialize')) return hasattr(obj, 'serialize') and hasattr(obj, 'deserialize') def is_sequence(obj): """Check if `obj` is a sequence, but not a string or bytes.""" return isinstance(obj, Sequence) and not BinaryClass.is_valid_type(obj) class List(list): """A sedes for lists, implemented as a list of other sedes objects. :param strict: If true (de)serializing lists that have a length not matching the sedes length will result in an error. If false (de)serialization will stop as soon as either one of the lists runs out of elements. """ def __init__(self, elements=[], strict=True): super(List, self).__init__() self.strict = strict for e in elements: if is_sedes(e): self.append(e) elif isinstance(e, Sequence): self.append(List(e)) else: raise TypeError('Instances of List must only contain sedes ' 'objects or nested sequences thereof.') def serialize(self, obj): if not is_sequence(obj): raise ListSerializationError('Can only serialize sequences', obj) if self.strict and len(self) != len(obj) or len(self) < len(obj): raise ListSerializationError('List has wrong length', obj) result = [] for index, (element, sedes) in enumerate(zip(obj, self)): try: result.append(sedes.serialize(element)) except SerializationError as e: raise ListSerializationError(obj=obj, element_exception=e, index=index) return result def deserialize(self, serial): if not is_sequence(serial): raise ListDeserializationError('Can only deserialize sequences', serial) result = [] element_iterator = iter(serial) sedes_iterator = iter(self) elements_consumed = False sedes_consumed = False for index in count(): try: element = next(element_iterator) except StopIteration: elements_consumed = True try: sedes = next(sedes_iterator) except StopIteration: sedes_consumed = True if not (sedes_consumed or elements_consumed): try: result.append(sedes.deserialize(element)) except DeserializationError as e: raise ListDeserializationError(serial=serial, element_exception=e, index=index) else: if self.strict and not (sedes_consumed and elements_consumed): raise ListDeserializationError('List has wrong length', serial) break return tuple(result) class CountableList(object): """A sedes for lists of arbitrary length. :param element_sedes: when (de-)serializing a list, this sedes will be applied to all of its elements :param max_length: maximum number of allowed elements, or `None` for no limit """ def __init__(self, element_sedes, max_length=None): self.element_sedes = element_sedes self.max_length = max_length def serialize(self, obj): if not is_sequence(obj): raise ListSerializationError('Can only serialize sequences', obj) result = [] for index, element in enumerate(obj): try: result.append(self.element_sedes.serialize(element)) except SerializationError as e: raise ListSerializationError(obj=obj, element_exception=e, index=index) if self.max_length is not None and len(result) > self.max_length: raise ListSerializationError('Too many elements ({}, allowed ' '{})'.format(len(result), self.max_length), obj) return result def deserialize(self, serial): if not is_sequence(serial): raise ListDeserializationError('Can only deserialize sequences', serial) result = [] for index, element in enumerate(serial): try: result.append(self.element_sedes.deserialize(element)) except DeserializationError as e: raise ListDeserializationError(serial=serial, element_exception=e, index=index) if self.max_length is not None and index >= self.max_length: raise ListDeserializationError('Too many elements (more than ' '{})'.format(self.max_length), serial) return tuple(result) class Serializable(object): """Base class for objects which can be serialized into RLP lists. :attr:`fields` defines which attributes are serialized and how this is done. It is expected to be an ordered sequence of 2-tuples ``(name, sedes)``. Here, ``name`` is the name of an attribute and ``sedes`` is the sedes object that will be used to serialize the corresponding attribute. The object as a whole is then serialized as a list of those fields. :cvar fields: a list of 2-tuples ``(name, sedes)`` where ``name`` is a string corresponding to an attribute and ``sedes`` is the sedes object used for (de)serializing the attribute. :param \*args: initial values for the first attributes defined via :attr:`fields` :param \*\*kwargs: initial values for all attributes not initialized via positional arguments :ivar _cached_rlp: can be used to store the object's RLP code (by default `None`) :ivar _mutable: if `False`, all attempts to set field values will fail (by default `True`, unless created with :meth:`deserialize`) """ fields = tuple() _sedes = None _mutable = True _cached_rlp = None def __init__(self, *args, **kwargs): # check keyword arguments are known field_set = set(field for field, _ in self.fields) # set positional arguments for (field, _), arg in zip(self.fields, args): setattr(self, field, arg) field_set.remove(field) # set keyword arguments, if not already set for (field, value) in kwargs.items(): if field in field_set: setattr(self, field, value) field_set.remove(field) if len(field_set) != 0: raise TypeError('Not all fields initialized') def __setattr__(self, attr, value): try: mutable = self.is_mutable() except AttributeError: mutable = True self.__dict__['_mutable'] = True # don't call __setattr__ again if mutable or attr not in set(field for field, _ in self.fields): super(Serializable, self).__setattr__(attr, value) else: raise ValueError('Tried to mutate immutable object') def __eq__(self, other): """Two objects are equal, if they are equal after serialization.""" if not hasattr(other.__class__, 'serialize'): return False return self.serialize(self) == other.serialize(other) def __ne__(self, other): return not self == other def is_mutable(self): """Checks if the object is mutable""" return self._mutable def make_immutable(self): """Make it immutable to prevent accidental changes. `obj.make_immutable` is equivalent to `make_immutable(obj)`, but doesn't return anything. """ make_immutable(self) def make_mutable(self): """Make it mutable. `obj.make_mutable` is equivalent to `make_mutable(obj)`, but doesn't return anything. """ make_mutable(self) @classmethod def get_sedes(cls): if not cls._sedes: cls._sedes = List(sedes for _, sedes in cls.fields) return cls._sedes @classmethod def serialize(cls, obj): try: field_values = [getattr(obj, field) for field, _ in cls.fields] except AttributeError: raise ObjectSerializationError('Cannot serialize this object (missing attribute)', obj) try: result = cls.get_sedes().serialize(field_values) except ListSerializationError as e: raise ObjectSerializationError(obj=obj, sedes=cls, list_exception=e) else: return result @classmethod def deserialize(cls, serial, exclude=None, mutable=False, **kwargs): try: values = cls.get_sedes().deserialize(serial) except ListDeserializationError as e: raise ObjectDeserializationError(serial=serial, sedes=cls, list_exception=e) params = { field: value for (field, _), value in zip(cls.fields, values) if not exclude or field not in exclude } obj = cls(**dict(list(params.items()) + list(kwargs.items()))) if mutable: return make_mutable(obj) else: return make_immutable(obj) @classmethod def exclude(cls, excluded_fields): """Create a new sedes considering only a reduced set of fields.""" class SerializableExcluded(cls): fields = [(field, sedes) for field, sedes in cls.fields if field not in excluded_fields] _sedes = None return SerializableExcluded def make_immutable(x): """Do your best to make `x` as immutable as possible. If `x` is a sequence, apply this function recursively to all elements and return a tuple containing them. If `x` is an instance of :class:`rlp.Serializable`, apply this function to its fields, and set :attr:`_mutable` to `False`. If `x` is neither of the above, just return `x`. :returns: `x` after making it immutable """ if isinstance(x, Serializable): x._mutable = True for field, _ in x.fields: attr = getattr(x, field) try: setattr(x, field, make_immutable(attr)) except AttributeError: pass # respect read only properties x._mutable = False return x elif is_sequence(x): return tuple(make_immutable(element) for element in x) else: return x def make_mutable(x): """Do your best to make `x` as mutable as possible. If `x` is a sequence, apply this function recursively to all elements and return a tuple containing them. If `x` is an instance of :class:`rlp.Serializable`, apply this function to its fields, and set :attr:`_mutable` to `False`. If `x` is neither of the above, just return `x`. :returns: `x` after making it mutable """ if isinstance(x, Serializable): x._mutable = True for field, _ in x.fields: attr = getattr(x, field) try: setattr(x, field, make_mutable(attr)) except AttributeError: pass # respect read only properties return x elif is_sequence(x): return list(make_mutable(element) for element in x) else: return x pyrlp-0.5.1/rlp/sedes/raw.py000066400000000000000000000013011313140545600157230ustar00rootroot00000000000000""" A sedes that does nothing. Thus, everything that can be directly encoded by RLP is serializable. This sedes can be used as a placeholder when deserializing larger structures. """ from collections import Sequence from ..exceptions import SerializationError from ..utils import Atomic def serializable(obj): if isinstance(obj, Atomic): return True elif isinstance(obj, Sequence): return all(map(serializable, obj)) else: return False def serialize(obj): if not serializable(obj): raise SerializationError('Can only serialize nested lists of strings', obj) return obj def deserialize(serial): return serial pyrlp-0.5.1/rlp/utils.py000066400000000000000000000001531313140545600151730ustar00rootroot00000000000000import sys if sys.version_info.major == 2: from .utils_py2 import * else: from .utils_py3 import * pyrlp-0.5.1/rlp/utils_py2.py000066400000000000000000000032641313140545600157730ustar00rootroot00000000000000import abc import struct import codecs import binascii class Atomic(object): """ABC for objects that can be RLP encoded as is.""" __metaclass__ = abc.ABCMeta Atomic.register(str) Atomic.register(bytearray) Atomic.register(unicode) bytes_to_str = str ascii_chr = chr def str_to_bytes(value): if isinstance(value, (bytes, bytearray)): return bytes(value) elif isinstance(value, unicode): return codecs.encode(value, 'utf8') else: raise TypeError("Value must be text, bytes, or bytearray") def _old_int_to_big_endian(value): cs = [] while value > 0: cs.append(chr(value % 256)) value /= 256 s = ''.join(reversed(cs)) return s def packl(lnum): if lnum == 0: return b'\0' s = hex(lnum)[2:] s = s.rstrip('L') if len(s) & 1: s = '0' + s s = binascii.unhexlify(s) return s int_to_big_endian = packl def big_endian_to_int(value): if len(value) == 1: return ord(value) elif len(value) <= 8: return struct.unpack('>Q', value.rjust(8, '\x00'))[0] else: return int(encode_hex(value), 16) def is_integer(value): return isinstance(value, (int, long)) def decode_hex(s): if isinstance(s, bytearray): s = str(s) if not isinstance(s, (str, unicode)): raise TypeError('Value must be an instance of str or unicode') return s.decode('hex') def encode_hex(s): if isinstance(s, bytearray): s = str(s) if not isinstance(s, (str, unicode)): raise TypeError('Value must be an instance of str or unicode') return s.encode('hex') def safe_ord(s): if isinstance(s, int): return s return ord(s) pyrlp-0.5.1/rlp/utils_py3.py000066400000000000000000000026321313140545600157720ustar00rootroot00000000000000import abc import binascii from math import ceil class Atomic(type.__new__(abc.ABCMeta, 'metaclass', (), {})): """ABC for objects that can be RLP encoded as is.""" pass Atomic.register(str) Atomic.register(bytes) def str_to_bytes(value): if isinstance(value, bytearray): value = bytes(value) if isinstance(value, bytes): return value return bytes(value, 'utf-8') def bytes_to_str(value): if isinstance(value, str): return value return value.decode('utf-8') def ascii_chr(value): return bytes([value]) def int_to_big_endian(value): byte_length = max(ceil(value.bit_length() / 8), 1) return (value).to_bytes(byte_length, byteorder='big') def big_endian_to_int(value): return int.from_bytes(value, byteorder='big') def is_integer(value): return isinstance(value, int) def decode_hex(s): if isinstance(s, str): return bytes.fromhex(s) if isinstance(s, (bytes, bytearray)): return binascii.unhexlify(s) raise TypeError('Value must be an instance of str or bytes') def encode_hex(b): if isinstance(b, str): b = bytes(b, 'utf-8') if isinstance(b, (bytes, bytearray)): return str(binascii.hexlify(b), 'utf-8') raise TypeError('Value must be an instance of str or bytes') def safe_ord(c): try: return ord(c) except TypeError: assert isinstance(c, int) return c pyrlp-0.5.1/setup.cfg000066400000000000000000000002331313140545600145040ustar00rootroot00000000000000[bumpversion] current_version = 0.5.1 commit = True tag = True [bumpversion:file:setup.py] search = version = "{current_version}" [wheel] universal = 1 pyrlp-0.5.1/setup.py000077500000000000000000000033621313140545600144060ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- try: from setuptools import setup except ImportError: from distutils.core import setup from setuptools.command.test import test as TestCommand class PyTest(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): # import here, cause outside the eggs aren't loaded import pytest pytest.main(self.test_args) with open('README.md') as readme_file: readme = readme_file.read() test_requirements = [ 'pytest', ] # *IMPORTANT*: Don't manually change the version here. Use the 'bumpversion' utility. # see: https://github.com/ethereum/pyethapp/wiki/Development:-Versions-and-Releases version = '0.5.1' setup( name='rlp', version=version, description="A package for encoding and decoding data in and from Recursive Length Prefix notation", long_description=readme, author="jnnk", author_email='jnnknnj@gmail.com', url='https://github.com/ethereum/pyrlp', packages=[ 'rlp', 'rlp.sedes' ], include_package_data=True, install_requires=[], license="MIT", zip_safe=False, keywords='rlp ethereum', classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', "Programming Language :: Python :: 2", 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', ], cmdclass={'test': PyTest}, tests_require=test_requirements ) pyrlp-0.5.1/tests/000077500000000000000000000000001313140545600140275ustar00rootroot00000000000000pyrlp-0.5.1/tests/rlptest.json000066400000000000000000000166721313140545600164330ustar00rootroot00000000000000{ "emptystring": { "in": "", "out": "80" }, "shortstring": { "in": "dog", "out": "83646f67" }, "shortstring2": { "in": "Lorem ipsum dolor sit amet, consectetur adipisicing eli", "out": "b74c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c69" }, "longstring": { "in": "Lorem ipsum dolor sit amet, consectetur adipisicing elit", "out": "b8384c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e7365637465747572206164697069736963696e6720656c6974" }, "longstring2": { "in": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur mauris magna, suscipit sed vehicula non, iaculis faucibus tortor. Proin suscipit ultricies malesuada. Duis tortor elit, dictum quis tristique eu, ultrices at risus. Morbi a est imperdiet mi ullamcorper aliquet suscipit nec lorem. Aenean quis leo mollis, vulputate elit varius, consequat enim. Nulla ultrices turpis justo, et posuere urna consectetur nec. Proin non convallis metus. Donec tempor ipsum in mauris congue sollicitudin. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse convallis sem vel massa faucibus, eget lacinia lacus tempor. Nulla quis ultricies purus. Proin auctor rhoncus nibh condimentum mollis. Aliquam consequat enim at metus luctus, a eleifend purus egestas. Curabitur at nibh metus. Nam bibendum, neque at auctor tristique, lorem libero aliquet arcu, non interdum tellus lectus sit amet eros. Cras rhoncus, metus ac ornare cursus, dolor justo ultrices metus, at ullamcorper volutpat", "out": "b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174" }, "zero": { "in": "", "out": "80" }, "smallint": { "in": 1, "out": "01" }, "smallint2": { "in": 16, "out": "10" }, "smallint3": { "in": 79, "out": "4f" }, "smallint4": { "in": 127, "out": "7f" }, "mediumint1": { "in": 128, "out": "8180" }, "mediumint2": { "in": 1000, "out": "8203e8" }, "mediumint3": { "in": 100000, "out": "830186a0" }, "mediumint4": { "in": 83729609699884896815286331701780722, "out": "8F102030405060708090A0B0C0D0E0F2" }, "mediumint5": { "in": 105315505618206987246253880190783558935785933862974822347068935681, "out": "9C0100020003000400050006000700080009000A000B000C000D000E01" }, "emptylist": { "in": [], "out": "c0" }, "stringlist": { "in": [ "dog", "god", "cat" ], "out": "cc83646f6783676f6483636174" }, "multilist": { "in": [ "zw", [ 4 ], 1 ], "out": "c6827a77c10401" }, "shortListMax1": { "in": [ "asdf", "qwer", "zxcv", "asdf","qwer", "zxcv", "asdf", "qwer", "zxcv", "asdf", "qwer"], "out": "F784617364668471776572847a78637684617364668471776572847a78637684617364668471776572847a78637684617364668471776572" }, "longList1" : { "in" : [ ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"] ], "out": "F840CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376" }, "longList2" : { "in" : [ ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"], ["asdf","qwer","zxcv"] ], "out": "F90200CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376CF84617364668471776572847a786376" }, "listsoflists": { "in": [ [ [], [] ], [] ], "out": "c4c2c0c0c0" }, "listsoflists2": { "in": [ [], [[]], [ [], [[]] ] ], "out": "c7c0c1c0c3c0c1c0" }, "dictTest1" : { "in" : [ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"], ["key4", "val4"] ], "out" : "ECCA846b6579318476616c31CA846b6579328476616c32CA846b6579338476616c33CA846b6579348476616c34" }, "bigint": { "in": 115792089237316195423570985008687907853269984665640564039457584007913129639936, "out": "a1010000000000000000000000000000000000000000000000000000000000000000" } } pyrlp-0.5.1/tests/speed.py000066400000000000000000000104171313140545600155040ustar00rootroot00000000000000""" util to benchmark known usecase """ import random import rlp from rlp.sedes import big_endian_int, BigEndianInt, Binary, binary, CountableList import time address = Binary.fixed_length(20, allow_empty=True) int20 = BigEndianInt(20) int32 = BigEndianInt(32) int256 = BigEndianInt(256) hash32 = Binary.fixed_length(32) trie_root = Binary.fixed_length(32, allow_empty=True) def zpad(x, l): return b'\x00' * max(0, l - len(x)) + x class Transaction(rlp.Serializable): fields = [ ('nonce', big_endian_int), ('gasprice', big_endian_int), ('startgas', big_endian_int), ('to', address), ('value', big_endian_int), ('data', binary), ('v', big_endian_int), ('r', big_endian_int), ('s', big_endian_int), ] def __init__(self, nonce, gasprice, startgas, to, value, data, v=0, r=0, s=0): super(Transaction, self).__init__(nonce, gasprice, startgas, to, value, data, v, r, s) class BlockHeader(rlp.Serializable): fields = [ ('prevhash', hash32), ('uncles_hash', hash32), ('coinbase', address), ('state_root', trie_root), ('tx_list_root', trie_root), ('receipts_root', trie_root), ('bloom', int256), ('difficulty', big_endian_int), ('number', big_endian_int), ('gas_limit', big_endian_int), ('gas_used', big_endian_int), ('timestamp', big_endian_int), ('extra_data', binary), ('mixhash', binary), ('nonce', binary) ] def __init__(self, *args, **kargs): super(BlockHeader, self).__init__(*args, **kargs) class Block(rlp.Serializable): fields = [ ('header', BlockHeader), ('transaction_list', CountableList(Transaction)), ('uncles', CountableList(BlockHeader)) ] def __init__(self, header, transaction_list=[], uncles=[]): super(Block, self).__init__(header, transaction_list, uncles) def rand_bytes(num=32): return zpad(big_endian_int.serialize(random.getrandbits(num * 8)), num) rand_bytes32 = rand_bytes def rand_address(): return rand_bytes(20) def rand_bytes8(): return rand_bytes(8) def rand_bigint(): return random.getrandbits(256) def rand_int(): return random.getrandbits(32) rand_map = {hash32: rand_bytes32, trie_root: rand_bytes32, binary: rand_bytes32, address: rand_address, Binary: rand_bytes8, big_endian_int: rand_int, int256: rand_bigint} assert Binary in rand_map def mk_transaction(): return Transaction(rand_int(), rand_int(), rand_int(), rand_address(), rand_int(), rand_bytes32(), 27, rand_bigint(), rand_bigint()) rlp.decode(rlp.encode(mk_transaction()), Transaction) def mk_block_header(): return BlockHeader(*[rand_map[t]() for _, t in BlockHeader.fields]) rlp.decode(rlp.encode(mk_block_header()), BlockHeader) def mk_block(num_transactions=10, num_uncles=1): return Block(mk_block_header(), [mk_transaction() for i in range(num_transactions)], [mk_block_header() for i in range(num_uncles)]) rlp.decode(rlp.encode(mk_block()), Block) def do_test_serialize(block, rounds=100): for i in range(rounds): x = rlp.encode(block) return x def do_test_deserialize(data, rounds=100, sedes=Block): for i in range(rounds): x = rlp.decode(data, sedes) return x def main(rounds=10000): st = time.time() d = do_test_serialize(mk_block(), rounds) elapsed = time.time() - st print 'Block serializations / sec: %.2f' % (rounds / elapsed) st = time.time() d = do_test_deserialize(d, rounds) elapsed = time.time() - st print 'Block deserializations / sec: %.2f' % (rounds / elapsed) st = time.time() d = do_test_serialize(mk_transaction(), rounds) elapsed = time.time() - st print 'TX serializations / sec: %.2f' % (rounds / elapsed) st = time.time() d = do_test_deserialize(d, rounds, sedes=Transaction) elapsed = time.time() - st print 'TX deserializations / sec: %.2f' % (rounds / elapsed) if __name__ == '__main__': main() """ py2 serializations / sec: 658.64 deserializations / sec: 1331.62 pypy2 serializations / sec: 4628.81 : x7 speedup deserializations / sec: 4753.84 : x3.5 speedup """ pyrlp-0.5.1/tests/test_benchmark.py000066400000000000000000000054071313140545600174000ustar00rootroot00000000000000from __future__ import unicode_literals from itertools import repeat, chain import sys import pytest import rlp from rlp.sedes import binary, CountableList from rlp.exceptions import DecodingError, DeserializationError try: import pytest_benchmark except ImportError: do_benchmark = False else: do_benchmark = True # speed up setup in case tests aren't run anyway if do_benchmark: SIZE = int(1e6) else: SIZE = 1 class Message(rlp.Serializable): fields = [ ('field1', binary), ('field2', binary), ('field3', CountableList(binary, max_length=100)) ] def lazy_test_factory(s, valid): @pytest.mark.benchmark(group='lazy') def f(benchmark): @benchmark def result(): try: Message.deserialize(rlp.decode_lazy(s)) except (DecodingError, DeserializationError): return not valid else: return valid assert result return f def eager_test_factory(s, valid): @pytest.mark.benchmark(group='eager') def f(benchmark): @benchmark def result(): try: rlp.decode(s, Message) except (DecodingError, DeserializationError): return not valid else: return valid assert result return f def generate_test_functions(): valid = {} invalid = {} long_string = bytes(bytearray((i % 256 for i in range(SIZE)))) long_list = rlp.encode([c for c in long_string]) invalid['long_string'] = long_string invalid['long_list'] = long_list nested_list = rlp.encode('\x00') for _ in repeat(None, SIZE): nested_list += rlp.codec.length_prefix(len(nested_list), 0xc0) invalid['nested_list'] = nested_list valid['long_string_object'] = rlp.encode([b'\x00', long_string, []]) prefix = rlp.codec.length_prefix(1 + 1 + len(long_list), 0xc0) invalid['long_list_object'] = prefix + rlp.encode(b'\x00') + rlp.encode(b'\x00') + long_list valid['friendly'] = rlp.encode(Message('hello', 'I\'m friendly', ['not', 'many', 'elements'])) invalid = invalid.items() valid = valid.items() rlp_strings = [i[1] for i in chain(valid, invalid)] valids = [True] * len(valid) + [False] * len(invalid) names = [i[0] for i in chain(valid, invalid)] current_module = sys.modules[__name__] for rlp_string, valid, name in zip(rlp_strings, valids, names): f_eager = pytest.mark.skipif('not do_benchmark')(eager_test_factory(rlp_string, valid)) f_lazy = pytest.mark.skipif('not do_benchmark')(lazy_test_factory(rlp_string, valid)) setattr(current_module, 'test_eager_' + name, f_eager) setattr(current_module, 'test_lazy_' + name, f_lazy) generate_test_functions() pyrlp-0.5.1/tests/test_big_endian.py000066400000000000000000000073251313140545600175260ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from rlp import SerializationError, utils from rlp.sedes import big_endian_int, BigEndianInt from rlp.utils import int_to_big_endian valid_data = ( (256, b'\x01\x00'), (1024, b'\x04\x00'), (65535, b'\xff\xff'), ) single_bytes = ((n, utils.ascii_chr(n)) for n in range(1, 256)) random_integers = (256, 257, 4839, 849302, 483290432, 483290483290482039482039, 48930248348219540325894323584235894327865439258743754893066) assert random_integers[-1] < 2**256 negative_ints = (-1, -100, -255, -256, -2342423) def test_neg(): for n in negative_ints: with pytest.raises(SerializationError): big_endian_int.serialize(n) def test_serialization(): for n in random_integers: serial = big_endian_int.serialize(n) deserialized = big_endian_int.deserialize(serial) assert deserialized == n if n != 0: assert serial[0] != b'\x00' # is not checked def test_single_byte(): for n, s in single_bytes: serial = big_endian_int.serialize(n) assert serial == s deserialized = big_endian_int.deserialize(serial) assert deserialized == n def test_valid_data(): for n, serial in valid_data: serialized = big_endian_int.serialize(n) deserialized = big_endian_int.deserialize(serial) assert serialized == serial assert deserialized == n def test_fixedlength(): s = BigEndianInt(4) for i in (0, 1, 255, 256, 256**3, 256**4 - 1): assert len(s.serialize(i)) == 4 assert s.deserialize(s.serialize(i)) == i for i in (256**4, 256**4 + 1, 256**5, -1, -256, 'asdf'): with pytest.raises(SerializationError): s.serialize(i) import binascii def packl(lnum): """Packs the lnum (which must be convertable to a long) into a byte string 0 padded to a multiple of padmultiple bytes in size. 0 means no padding whatsoever, so that packing 0 result in an empty string. The resulting byte string is the big-endian two's complement representation of the passed in long.""" if lnum == 0: return b'\0' s = hex(lnum)[2:] s = s.rstrip('L') if len(s) & 1: s = '0' + s s = binascii.unhexlify(s) return s try: import ctypes PyLong_AsByteArray = ctypes.pythonapi._PyLong_AsByteArray PyLong_AsByteArray.argtypes = [ctypes.py_object, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_int, ctypes.c_int] import sys long_start = sys.maxint + 1 def packl_ctypes(lnum): if lnum < long_start: return int_to_big_endian(lnum) a = ctypes.create_string_buffer(lnum.bit_length() // 8 + 1) PyLong_AsByteArray(lnum, a, len(a), 0, 1) return a.raw.lstrip(b'\0') except AttributeError: packl_ctypes = packl def test_packl(): for i in range(256): v = 2**i - 1 rc = packl_ctypes(v) assert rc == int_to_big_endian(v) r = packl(v) assert r == int_to_big_endian(v) def perf(): import time st = time.time() for j in range(100000): for i in random_integers: packl(i) print('packl elapsed {}'.format(time.time() - st)) st = time.time() for j in range(100000): for i in random_integers: packl_ctypes(i) print('ctypes elapsed {}'.format(time.time() - st)) st = time.time() for j in range(100000): for i in random_integers: int_to_big_endian(i) print('py elapsed {}'.format(time.time() - st)) if __name__ == '__main__': # test_packl() perf() pyrlp-0.5.1/tests/test_binary_sedes.py000066400000000000000000000036761313140545600201230ustar00rootroot00000000000000# -*- coding: UTF-8 -*- from __future__ import unicode_literals import pytest from rlp import SerializationError, utils from rlp.sedes import Binary def test_binary(): b1 = Binary() f = { '': b'', 'asdf': b'asdf', ('\x00' * 20): (b'\x00' * 20), 'fdsa': b'fdsa' } for k in f: assert b1.serialize(k) == f[k] for d in ([], 5, str): with pytest.raises(SerializationError): b1.serialize(d) b2 = Binary.fixed_length(5) f = { 'asdfg': b'asdfg', b'\x00\x01\x02\x03\x04': b'\x00\x01\x02\x03\x04', utils.str_to_bytes('ababa'): b'ababa' } for k in f: assert b2.serialize(k) == f[k] for d in ('asdf', 'asdfgh', '', 'bababa'): with pytest.raises(SerializationError): b2.serialize(d) b3 = Binary(2, 4) f = { 'as': b'as', 'dfg': b'dfg', 'hjkl': b'hjkl', b'\x00\x01\x02': b'\x00\x01\x02' } for k in f: assert b3.serialize(k) == f[k] for d in ('', 'a', 'abcde', 'äää'): with pytest.raises(SerializationError): b3.serialize(d) b4 = Binary(min_length=3) f = {'abc': b'abc', 'abcd': b'abcd', ('x' * 132): (b'x' * 132)} for k in f: assert b4.serialize(k) == f[k] for d in ('ab', '', 'a', 'xy'): with pytest.raises(SerializationError): b4.serialize(d) b5 = Binary(max_length=3) f = {'': b'', 'ab': b'ab', 'abc': b'abc'} for k in f: assert b5.serialize(k) == f[k] for d in ('abcd', 'vwxyz', 'a' * 32): with pytest.raises(SerializationError): b5.serialize(d) b6 = Binary(min_length=3, max_length=5, allow_empty=True) f = {'': b'', 'abc': b'abc', 'abcd': b'abcd', 'abcde': b'abcde'} for k in f: assert b6.serialize(k) == f[k] for d in ('a', 'ab', 'abcdef', 'abcdefgh' * 10): with pytest.raises(SerializationError): b6.serialize(d) pyrlp-0.5.1/tests/test_bytearray.py000066400000000000000000000014501313140545600174420ustar00rootroot00000000000000# -*- coding: utf8 -*- from __future__ import unicode_literals import struct import rlp def test_bytearray(): e = rlp.encode('abc') d = rlp.decode(e) d = rlp.decode(bytearray(e)) def test_bytearray_lazy(): e = rlp.encode('abc') d = rlp.decode(e) d = rlp.decode_lazy(bytearray(e)) def test_bytearray_encode_decode(): value = bytearray(b'asdf') encoded = rlp.utils.encode_hex(value) decoded = rlp.utils.decode_hex(encoded) assert value == decoded def test_big_endian_to_int(): assert rlp.utils.big_endian_to_int(b'\x00') == 0 assert rlp.utils.big_endian_to_int(bytearray(b'\x00')) == 0 value = struct.pack('>Q', 3141516) assert rlp.utils.big_endian_to_int(value) == 3141516 assert rlp.utils.big_endian_to_int(bytearray(value)) == 3141516 pyrlp-0.5.1/tests/test_codec.py000066400000000000000000000012011313140545600165070ustar00rootroot00000000000000import pytest import rlp def test_compare_length(): data = rlp.encode([1,2,3,4,5]) assert rlp.compare_length(data, 100) == -1 assert rlp.compare_length(data, 5) == 0 assert rlp.compare_length(data, 1) == 1 data = rlp.encode([]) assert rlp.compare_length(data, 100) == -1 assert rlp.compare_length(data, 0) == 0 assert rlp.compare_length(data, -1) == 1 def test_favor_short_string_form(): data = rlp.utils.decode_hex('b8056d6f6f7365') with pytest.raises(rlp.exceptions.DecodingError): rlp.decode(data) data = rlp.utils.decode_hex('856d6f6f7365') assert rlp.decode(data) == b'moose' pyrlp-0.5.1/tests/test_countablelist.py000066400000000000000000000033111313140545600203060ustar00rootroot00000000000000from __future__ import unicode_literals import pytest import rlp from rlp.sedes import big_endian_int from rlp.sedes.lists import CountableList from rlp import SerializationError, DeserializationError def test_countable_list(): l1 = CountableList(big_endian_int) serializable = [(), (1, 2), tuple(range(500))] for s in serializable: r = l1.serialize(s) assert l1.deserialize(r) == s not_serializable = ([1, 'asdf'], ['asdf'], [1, [2]], [[]]) for n in not_serializable: with pytest.raises(SerializationError): l1.serialize(n) l2 = CountableList(CountableList(big_endian_int)) serializable = ((), ((),), ((1, 2, 3), (4,)), ((5,), (6, 7, 8)), ((), (), (9, 0))) for s in serializable: r = l2.serialize(s) assert l2.deserialize(r) == s not_serializable = ([[[]]], [1, 2], [1, ['asdf'], ['fdsa']]) for n in not_serializable: with pytest.raises(SerializationError): l2.serialize(n) l3 = CountableList(big_endian_int, max_length=3) serializable = [(), (1,), (1, 2), (1, 2, 3)] for s in serializable: r = l3.serialize(s) assert r == l1.serialize(s) assert l3.deserialize(r) == s not_serializable = [(1, 2, 3, 4), (1, 2, 3, 4, 5, 6, 7), range(500)] for s in not_serializable: with pytest.raises(SerializationError): l3.serialize(s) r = l1.serialize(s) with pytest.raises(DeserializationError): l3.deserialize(r) ll = rlp.decode_lazy(rlp.encode(r)) with pytest.raises(DeserializationError): l3.deserialize(ll) assert len(ll._elements) == 3 + 1 # failed early, did not consume fully pyrlp-0.5.1/tests/test_invalid.py000066400000000000000000000007661313140545600170770ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from rlp import decode, DecodingError invalid_rlp = ( b'', b'\x00\xab', b'\x00\x00\xff', b'\x83dogcat', b'\x83do', b'\xc7\xc0\xc1\xc0\xc3\xc0\xc1\xc0\xff', b'\xc7\xc0\xc1\xc0\xc3\xc0\xc1' b'\x81\x02', b'\xb8\x00', b'\xb9\x00\x00', b'\xba\x00\x02\xff\xff', b'\x81\x54' ) def test_invalid_rlp(): for serial in invalid_rlp: with pytest.raises(DecodingError): decode(serial) pyrlp-0.5.1/tests/test_json.py000066400000000000000000000044411313140545600164140ustar00rootroot00000000000000from __future__ import unicode_literals import json import sys import pytest import rlp from rlp import encode, decode, decode_lazy, infer_sedes, utils, DecodingError if sys.version_info.major == 2: str_types = (str, unicode) elif sys.version_info.major == 3: str_types = bytes else: assert False def evaluate(ll): if isinstance(ll, rlp.lazy.LazyList): return [evaluate(e) for e in ll] else: return ll def to_bytes(value): if isinstance(value, str): return utils.str_to_bytes(value) elif isinstance(value, list): return [to_bytes(v) for v in value] else: return value def compare_nested(got, expected): if isinstance(got, str_types): return got == expected try: zipped = zip(got, expected) except TypeError: return got == expected else: if len(list(zipped)) == len(got) == len(expected): return all(compare_nested(x, y) for x, y in zipped) else: return False with open('tests/rlptest.json') as f: test_data = json.loads(f.read()) test_pieces = [(name, {'in': to_bytes(in_out['in']), 'out': in_out['out']}) for name, in_out in test_data.items()] @pytest.mark.parametrize('name, in_out', test_pieces) def test_encode(name, in_out): msg_format = 'Test {} failed (encoded {} to {} instead of {})' data = in_out['in'] result = utils.encode_hex(encode(data)).upper() expected = in_out['out'].upper() if result != expected: pytest.fail(msg_format.format(name, data, result, expected)) @pytest.mark.parametrize('name, in_out', test_pieces) def test_decode(name, in_out): msg_format = 'Test {} failed (decoded {} to {} instead of {})' rlp_string = utils.decode_hex(in_out['out']) decoded = decode(rlp_string) with pytest.raises(DecodingError): decode(rlp_string + b'\x00') assert decoded == decode(rlp_string + b'\x00', strict=False) assert decoded == evaluate(decode_lazy(rlp_string)) expected = in_out['in'] sedes = infer_sedes(expected) data = sedes.deserialize(decoded) assert compare_nested(data, decode(rlp_string, sedes)) if not compare_nested(data, expected): pytest.fail(msg_format.format(name, rlp_string, decoded, expected)) pyrlp-0.5.1/tests/test_lazy.py000066400000000000000000000044231313140545600164220ustar00rootroot00000000000000from __future__ import unicode_literals from collections import Sequence import pytest import rlp from rlp import DeserializationError from rlp.sedes import big_endian_int, CountableList def evaluate(lazy_list): if isinstance(lazy_list, rlp.lazy.LazyList): return tuple(evaluate(e) for e in lazy_list) else: return lazy_list def test_empty_list(): dec = lambda: rlp.decode_lazy(rlp.encode([])) assert isinstance(dec(), Sequence) with pytest.raises(IndexError): dec()[0] with pytest.raises(IndexError): dec()[1] assert len(dec()) == 0 assert evaluate(dec()) == () def test_string(): for s in (b'', b'asdf', b'a' * 56, b'b' * 123): dec = lambda: rlp.decode_lazy(rlp.encode(s)) assert isinstance(dec(), bytes) assert len(dec()) == len(s) assert dec() == s assert rlp.peek(rlp.encode(s), []) == s with pytest.raises(IndexError): rlp.peek(rlp.encode(s), 0) with pytest.raises(IndexError): rlp.peek(rlp.encode(s), [0]) def test_nested_list(): l = ((), (b'a'), (b'b', b'c', b'd')) dec = lambda: rlp.decode_lazy(rlp.encode(l)) assert isinstance(dec(), Sequence) assert len(dec()) == len(l) assert evaluate(dec()) == l with pytest.raises(IndexError): dec()[0][0] with pytest.raises(IndexError): dec()[1][1] with pytest.raises(IndexError): dec()[2][3] with pytest.raises(IndexError): dec()[3] def test_sedes(): ls = [ (), (1,), (3, 2, 1) ] for l in ls: assert evaluate(rlp.decode_lazy(rlp.encode(l), big_endian_int)) == l sedes = CountableList(big_endian_int) l = [(), (1, 2), 'asdf', (3)] invalid_lazy = rlp.decode_lazy(rlp.encode(l), sedes) assert invalid_lazy[0] == l[0] assert invalid_lazy[1] == l[1] with pytest.raises(DeserializationError): invalid_lazy[2] def test_peek(): assert rlp.peek(rlp.encode(b''), []) == b'' nested = rlp.encode([0, 1, [2, 3]]) assert rlp.peek(nested, [2, 0], big_endian_int) == 2 for index in [3, [3], [0, 0], [2, 2], [2, 1, 0]]: with pytest.raises(IndexError): rlp.peek(nested, index) assert rlp.peek(nested, 2, CountableList(big_endian_int)) == (2, 3) pyrlp-0.5.1/tests/test_raw_sedes.py000066400000000000000000000011471313140545600174170ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from rlp import encode, decode, SerializationError from rlp.sedes import raw serializable = ( b'', b'asdf', b'fds89032#$@%', b'', b'dfsa', [b'dfsa', b''], [], [b'fdsa', [b'dfs', [b'jfdkl']]], ) not_serializable = ( 0, 32, ['asdf', ['fdsa', [5]]], str ) def test_serializable(): for s in serializable: raw.serialize(s) code = encode(s, raw) assert s == decode(code, raw) for s in not_serializable: with pytest.raises(SerializationError): raw.serialize(s) pyrlp-0.5.1/tests/test_sedes.py000066400000000000000000000036501313140545600165470ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from rlp import SerializationError, DeserializationError from rlp import infer_sedes from rlp.sedes import big_endian_int, binary, List, CountableList def test_inference(): obj_sedes_pairs = ( (5, big_endian_int), (0, big_endian_int), (-1, None), ('', binary), ('asdf', binary), ('\xe4\xf6\xfc\xea\xe2\xfb', binary), ([], List()), ([1, 2, 3], List((big_endian_int,) * 3)), ([[], 'asdf'], List(([], binary))), ) for obj, sedes in obj_sedes_pairs: if sedes is not None: inferred = infer_sedes(obj) assert inferred == sedes sedes.serialize(obj) else: with pytest.raises(TypeError): infer_sedes(obj) def test_list_sedes(): l1 = List() l2 = List((big_endian_int, big_endian_int)) l3 = List((l1, l2, [[[]]])) l1.serialize([]) l2.serialize((2, 3)) l3.serialize([[], [5, 6], [[[]]]]) with pytest.raises(SerializationError): l1.serialize([[]]) with pytest.raises(SerializationError): l1.serialize([5]) for d in ([], [1, 2, 3], [1, [2, 3], 4]): with pytest.raises(SerializationError): l2.serialize(d) for d in ([], [[], [], [[[]]]], [[], [5, 6], [[]]]): with pytest.raises(SerializationError): l3.serialize(d) c = CountableList(big_endian_int) assert l1.deserialize(c.serialize([])) == () for s in (c.serialize(l) for l in [[1], [1, 2, 3], range(30), (4, 3)]): with pytest.raises(DeserializationError): l1.deserialize(s) valid = [(1, 2), (3, 4), (9, 8)] for s, v in ((c.serialize(v), v) for v in valid): assert l2.deserialize(s) == v invalid = [[], [1], [1, 2, 3]] for s in (c.serialize(i) for i in invalid): with pytest.raises(DeserializationError): l2.deserialize(s) pyrlp-0.5.1/tests/test_serializable.py000066400000000000000000000134711313140545600201140ustar00rootroot00000000000000from __future__ import unicode_literals import pytest from rlp import SerializationError from rlp import infer_sedes, Serializable, encode, decode, make_immutable, make_mutable from rlp.sedes import big_endian_int, binary, List class Test1(Serializable): fields = [ ('field1', big_endian_int), ('field2', binary), ('field3', List((big_endian_int, binary))) ] class Test2(Serializable): fields = [ ('field1', Test1), ('field2', List((Test1, Test1))), ] def test_serializable(): t1a_data = (5, 'a', (0, '')) t1b_data = (9, 'b', (2, '')) test1a = Test1(*t1a_data) test1b = Test1(*t1b_data) test2 = Test2(test1a, [test1a, test1b]) # equality assert test1a == test1a assert test1b == test1b assert test2 == test2 assert test1a != test1b assert test1b != test2 assert test2 != test1a # mutability test1a.field1 += 1 test1a.field2 = 'x' assert test1a.field1 == 6 assert test1a.field2 == 'x' test1a.field1 -= 1 test1a.field2 = 'a' assert test1a.field1 == 5 assert test1a.field2 == 'a' # inference assert infer_sedes(test1a) == Test1 assert infer_sedes(test1b) == Test1 assert infer_sedes(test2) == Test2 # serialization with pytest.raises(SerializationError): Test1.serialize(test2) with pytest.raises(SerializationError): Test2.serialize(test1a) with pytest.raises(SerializationError): Test2.serialize(test1b) serial_1a = Test1.serialize(test1a) serial_1b = Test1.serialize(test1b) serial_2 = Test2.serialize(test2) assert serial_1a == [b'\x05', b'a', [b'', b'']] assert serial_1b == [b'\x09', b'b', [b'\x02', b'']] assert serial_2 == [serial_1a, [serial_1a, serial_1b]] # deserialization test1a_d = Test1.deserialize(serial_1a) test1b_d = Test1.deserialize(serial_1b) test2_d = Test2.deserialize(serial_2, mutable=True) assert not test1a_d.is_mutable() assert not test1b_d.is_mutable() assert test2_d.is_mutable() for obj in (test1a_d, test1b_d): before1 = obj.field1 before2 = obj.field2 with pytest.raises(ValueError): obj.field1 += 1 with pytest.raises(ValueError): obj.field2 = 'x' assert obj.field1 == before1 assert obj.field2 == before2 assert test1a_d == test1a assert test1b_d == test1b assert test2_d == test2 # encoding and decoding for obj in (test1a, test1b, test2): rlp_code = encode(obj) assert obj._cached_rlp is None assert obj.is_mutable() assert encode(obj, cache=True) == rlp_code assert obj._cached_rlp == rlp_code assert not obj.is_mutable() assert encode(obj, cache=True) == rlp_code assert obj._cached_rlp == rlp_code assert not obj.is_mutable() assert encode(obj) == rlp_code assert obj._cached_rlp == rlp_code assert not obj.is_mutable() obj_decoded = decode(rlp_code, obj.__class__) assert obj_decoded == obj assert not obj_decoded.is_mutable() assert obj_decoded._cached_rlp == rlp_code def test_make_immutable(): assert make_immutable(1) == 1 assert make_immutable('a') == 'a' assert make_immutable((1, 2, 3)) == (1, 2, 3) assert make_immutable([1, 2, 'a']) == (1, 2, 'a') assert make_immutable([[1], [2, [3], 4], 5, 6]) == ((1,), (2, (3,), 4), 5, 6) t1a_data = (5, 'a', (0, '')) t1b_data = (9, 'b', (2, '')) test1a = Test1(*t1a_data) test1b = Test1(*t1b_data) test2 = Test2(test1a, [test1a, test1b]) assert test2.is_mutable() assert test2.field1.is_mutable() assert test2.field2[0].is_mutable() assert test2.field2[1].is_mutable() test2.make_immutable() assert not test2.is_mutable() assert not test1a.is_mutable() assert not test1b.is_mutable() assert test2.field1 == test1a assert test2.field2 == (test1a, test1b) test1a = Test1(*t1a_data) test1b = Test1(*t1b_data) test2 = Test2(test1a, [test1a, test1b]) assert test2.is_mutable() assert test2.field1.is_mutable() assert test2.field2[0].is_mutable() assert test2.field2[1].is_mutable() assert make_immutable([test1a, [test2, test1b]]) == (test1a, (test2, test1b)) assert not test2.is_mutable() assert not test1a.is_mutable() assert not test1b.is_mutable() def test_make_mutable(): assert make_mutable(1) == 1 assert make_mutable('a') == 'a' assert make_mutable((1, 2, 3)) == [1, 2, 3] assert make_mutable([1, 2, 'a']) == [1, 2, 'a'] assert make_mutable([[1], [2, [3], 4], 5, 6]) == [[1,], [2, [3,], 4], 5, 6] t1a_data = (5, 'a', (0, '')) t1b_data = (9, 'b', (2, '')) test1a = Test1(*t1a_data) test1b = Test1(*t1b_data) test2 = Test2(test1a, [test1a, test1b]) test1a.make_immutable() test1b.make_immutable() test2.make_immutable() assert not test2.is_mutable() assert not test2.field1.is_mutable() assert not test2.field2[0].is_mutable() assert not test2.field2[1].is_mutable() test2.make_mutable() assert test2.is_mutable() assert test2.field2[0].is_mutable() assert test2.field2[1].is_mutable() assert test1a.is_mutable() assert test1b.is_mutable() assert test2.field1 == test1a assert test2.field2 == [test1a, test1b] test1a = Test1(*t1a_data) test1b = Test1(*t1b_data) test2 = Test2(test1a, [test1a, test1b]) test1a.make_immutable() test1b.make_immutable() test2.make_immutable() assert not test2.is_mutable() assert not test2.field1.is_mutable() assert not test2.field2[0].is_mutable() assert not test2.field2[1].is_mutable() assert make_mutable([test1a, [test2, test1b]]) == [test1a, [test2, test1b]] assert test2.is_mutable() assert test1a.is_mutable() assert test1b.is_mutable() pyrlp-0.5.1/tox.ini000066400000000000000000000007301313140545600142000ustar00rootroot00000000000000[tox] envlist = py{27,34,35,py,py3} [testenv] passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH setenv = PYTHONPATH = {toxinidir}:{toxinidir}/pyrlp commands = coverage run --parallel --source=rlp --branch -m py.test {posargs} coverage combine coverage report --show-missing deps = -r{toxinidir}/requirements.txt pytest==2.8.4 coverage==4.0.3 basepython = py27: python2.7 py34: python3.4 py35: python3.5 pypy: pypy pypy3: pypy3