├── .bumpversion.cfg ├── .circleci ├── config.yml └── merge_pr.sh ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .project-template ├── fill_template_vars.sh ├── refill_template_vars.sh └── template_vars.txt ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Makefile ├── _static │ └── .suppress-sphinx-build-warning ├── conf.py ├── index.rst └── releases.rst ├── py_snappy ├── __init__.py ├── constants.py ├── exceptions.py └── main.py ├── pytest.ini ├── requirements-docs.txt ├── setup.py ├── tests ├── core │ ├── conftest.py │ ├── strategies.py │ ├── test_empty_bytestring.py │ ├── test_import.py │ ├── test_libsnappy_compat.py │ ├── test_official_test_vectors.py │ └── test_round_trip.py └── fixtures │ ├── alice29.txt │ ├── asyoulik.txt │ ├── baddata1.snappy │ ├── baddata2.snappy │ ├── baddata3.snappy │ ├── fireworks.jpeg │ ├── geo.protodata │ ├── html │ ├── html_x_4 │ ├── kppkn.gtb │ ├── lcet10.txt │ ├── paper-100k.pdf │ ├── plrabn12.txt │ └── urls.10K └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0-alpha.1 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P[^.]*)\.(?P\d+))? 6 | serialize = 7 | {major}.{minor}.{patch}-{stage}.{devnum} 8 | {major}.{minor}.{patch} 9 | 10 | [bumpversion:part:stage] 11 | optional_value = stable 12 | first_value = stable 13 | values = 14 | alpha 15 | beta 16 | stable 17 | 18 | [bumpversion:part:devnum] 19 | 20 | [bumpversion:file:setup.py] 21 | search = version='{current_version}', 22 | replace = version='{new_version}', 23 | 24 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | 3 | # heavily inspired by https://raw.githubusercontent.com/pinax/pinax-wiki/6bd2a99ab6f702e300d708532a6d1d9aa638b9f8/.circleci/config.yml 4 | 5 | common: &common 6 | working_directory: ~/repo 7 | steps: 8 | - checkout 9 | - run: 10 | name: merge pull request base 11 | command: ./.circleci/merge_pr.sh 12 | - run: 13 | name: merge pull request base (2nd try) 14 | command: ./.circleci/merge_pr.sh 15 | when: on_fail 16 | - run: 17 | name: merge pull request base (3nd try) 18 | command: ./.circleci/merge_pr.sh 19 | when: on_fail 20 | - restore_cache: 21 | keys: 22 | - cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} 23 | - run: 24 | name: install libsnappy-dev 25 | command: sudo apt install -y libsnappy-dev 26 | - run: 27 | name: install dependencies 28 | command: pip install --user tox 29 | - run: 30 | name: run tox 31 | command: ~/.local/bin/tox -r 32 | - save_cache: 33 | paths: 34 | - .hypothesis 35 | - .tox 36 | - ~/.cache/pip 37 | - ~/.local 38 | - ./eggs 39 | key: cache-{{ .Environment.CIRCLE_JOB }}-{{ checksum "setup.py" }}-{{ checksum "tox.ini" }} 40 | 41 | jobs: 42 | doctest: 43 | <<: *common 44 | docker: 45 | - image: circleci/python:3.6 46 | environment: 47 | TOXENV: doctest 48 | lint: 49 | <<: *common 50 | docker: 51 | - image: circleci/python:3.6 52 | environment: 53 | TOXENV: lint 54 | py36-core: 55 | <<: *common 56 | docker: 57 | - image: circleci/python:3.6 58 | environment: 59 | TOXENV: py36-core 60 | py37-core: 61 | <<: *common 62 | docker: 63 | - image: circleci/python:3.7 64 | environment: 65 | TOXENV: py37-core 66 | workflows: 67 | version: 2 68 | test: 69 | jobs: 70 | - doctest 71 | - lint 72 | - py36-core 73 | - py37-core 74 | -------------------------------------------------------------------------------- /.circleci/merge_pr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n "${CIRCLE_PR_NUMBER}" ]]; then 4 | PR_INFO_URL=https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$CIRCLE_PR_NUMBER 5 | PR_BASE_BRANCH=$(curl -L "$PR_INFO_URL" | python -c 'import json, sys; obj = json.load(sys.stdin); sys.stdout.write(obj["base"]["ref"])') 6 | git fetch origin +"$PR_BASE_BRANCH":circleci/pr-base 7 | # We need these config values or git complains when creating the 8 | # merge commit 9 | git config --global user.name "Circle CI" 10 | git config --global user.email "circleci@example.com" 11 | git merge --no-edit circleci/pr-base 12 | fi 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _If this is a bug report, please fill in the following sections. 2 | If this is a feature request, delete and describe what you would like with examples._ 3 | 4 | ## What was wrong? 5 | 6 | ### Code that produced the error 7 | 8 | ```py 9 | CODE_TO_REPRODUCE 10 | ``` 11 | 12 | ### Full error output 13 | 14 | ```sh 15 | ERROR_HERE 16 | ``` 17 | 18 | ### Expected Result 19 | 20 | _This section may be deleted if the expectation is "don't crash"._ 21 | 22 | ```sh 23 | EXPECTED_RESULT 24 | ``` 25 | 26 | ### Environment 27 | 28 | ```sh 29 | # run this: 30 | $ python -m eth_utils 31 | 32 | # then copy the output here: 33 | OUTPUT_HERE 34 | ``` 35 | 36 | ## How can it be fixed? 37 | 38 | Fill this section in if you know how this could or should be fixed. 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What was wrong? 2 | 3 | Issue # 4 | 5 | ## How was it fixed? 6 | 7 | Summary of approach. 8 | 9 | #### Cute Animal Picture 10 | 11 | ![put a cute animal picture link inside the parentheses]() 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | .eggs 13 | parts 14 | bin 15 | var 16 | sdist 17 | develop-eggs 18 | .installed.cfg 19 | lib 20 | lib64 21 | venv* 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # Complexity 40 | output/*.html 41 | output/*/index.html 42 | 43 | # Sphinx 44 | docs/_build 45 | docs/modules.rst 46 | docs/*.internal.rst 47 | docs/*.utils.rst 48 | docs/*._utils.* 49 | 50 | # Blockchain 51 | chains 52 | 53 | # Hypothese Property base testing 54 | .hypothesis 55 | 56 | # tox/pytest cache 57 | .cache 58 | 59 | # Test output logs 60 | logs 61 | ### JetBrains template 62 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 63 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 64 | 65 | # User-specific stuff: 66 | .idea/workspace.xml 67 | .idea/tasks.xml 68 | .idea/dictionaries 69 | .idea/vcs.xml 70 | .idea/jsLibraryMappings.xml 71 | 72 | # Sensitive or high-churn files: 73 | .idea/dataSources.ids 74 | .idea/dataSources.xml 75 | .idea/dataSources.local.xml 76 | .idea/sqlDataSources.xml 77 | .idea/dynamic.xml 78 | .idea/uiDesigner.xml 79 | 80 | # Gradle: 81 | .idea/gradle.xml 82 | .idea/libraries 83 | 84 | # Mongo Explorer plugin: 85 | .idea/mongoSettings.xml 86 | 87 | # VIM temp files 88 | *.swp 89 | 90 | ## File-based project format: 91 | *.iws 92 | 93 | ## Plugin-specific files: 94 | 95 | # IntelliJ 96 | /out/ 97 | 98 | # mpeltonen/sbt-idea plugin 99 | .idea_modules/ 100 | 101 | # JIRA plugin 102 | atlassian-ide-plugin.xml 103 | 104 | # Crashlytics plugin (for Android Studio and IntelliJ) 105 | com_crashlytics_export_strings.xml 106 | crashlytics.properties 107 | crashlytics-build.properties 108 | fabric.properties 109 | 110 | -------------------------------------------------------------------------------- /.project-template/fill_template_vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | PROJECT_ROOT=$(dirname $(dirname $(python -c 'import os, sys; sys.stdout.write(os.path.realpath(sys.argv[1]))' "$0"))) 8 | 9 | echo "What is your python module name?" 10 | read MODULE_NAME 11 | 12 | echo "What is your pypi package name? (default: $MODULE_NAME)" 13 | read PYPI_INPUT 14 | PYPI_NAME=${PYPI_INPUT:-$MODULE_NAME} 15 | 16 | echo "What is your github project name? (default: $PYPI_NAME)" 17 | read REPO_INPUT 18 | REPO_NAME=${REPO_INPUT:-$PYPI_NAME} 19 | 20 | echo "What is your readthedocs.org project name? (default: $PYPI_NAME)" 21 | read RTD_INPUT 22 | RTD_NAME=${RTD_INPUT:-$PYPI_NAME} 23 | 24 | echo "What is your project name (ex: at the top of the README)? (default: $REPO_NAME)" 25 | read PROJECT_INPUT 26 | PROJECT_NAME=${PROJECT_INPUT:-$REPO_NAME} 27 | 28 | echo "What is a one-liner describing the project?" 29 | read SHORT_DESCRIPTION 30 | 31 | _replace() { 32 | local find_cmd=(find "$PROJECT_ROOT" ! -perm -u=x ! -path '*/.git/*' -type f) 33 | 34 | if [[ $(uname) == Darwin ]]; then 35 | "${find_cmd[@]}" -exec sed -i '' "$1" {} + 36 | else 37 | "${find_cmd[@]}" -exec sed -i "$1" {} + 38 | fi 39 | } 40 | _replace "s//$MODULE_NAME/g" 41 | _replace "s//$PYPI_NAME/g" 42 | _replace "s//$REPO_NAME/g" 43 | _replace "s//$RTD_NAME/g" 44 | _replace "s//$PROJECT_NAME/g" 45 | _replace "s//$SHORT_DESCRIPTION/g" 46 | 47 | mkdir -p "$PROJECT_ROOT/$MODULE_NAME" 48 | touch "$PROJECT_ROOT/$MODULE_NAME/__init__.py" 49 | -------------------------------------------------------------------------------- /.project-template/refill_template_vars.sh: -------------------------------------------------------------------------------- 1 | TEMPLATE_DIR=$(dirname $(readlink -f "$0")) 2 | <"$TEMPLATE_DIR/template_vars.txt" "$TEMPLATE_DIR/fill_template_vars.sh" 3 | -------------------------------------------------------------------------------- /.project-template/template_vars.txt: -------------------------------------------------------------------------------- 1 | py_snappy 2 | py-snappy 3 | py-snappy 4 | py-snappy 5 | py-snappy 6 | A pure python implementation of the Snappy compression algorithm 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | dist: trusty 4 | matrix: 5 | include: 6 | # 7 | # Python 3.5 testing 8 | # 9 | # lint 10 | - python: "3.5" 11 | env: TOX_POSARGS="-e lint" 12 | # doctest 13 | - python: "3.5" 14 | env: TOX_POSARGS="-e doctest" 15 | # core 16 | - python: "3.5" 17 | env: TOX_POSARGS="-e py35-core" 18 | # 19 | # Python 3.6 testing 20 | # 21 | # core 22 | - python: "3.6" 23 | env: TOX_POSARGS="-e py36-core" 24 | # 25 | # pypy3 testing 26 | # 27 | # core 28 | - python: "pypy3.5" 29 | env: TOX_POSARGS="-e pypy3-core" 30 | cache: 31 | - pip: true 32 | install: 33 | - travis_retry pip install pip setuptools --upgrade 34 | - travis_retry pip install tox 35 | before_script: 36 | - python --version 37 | - pip --version 38 | - pip freeze 39 | script: 40 | - tox $TOX_POSARGS 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Jason Carver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CURRENT_SIGN_SETTING := $(shell git config commit.gpgSign) 2 | 3 | .PHONY: clean-pyc clean-build docs 4 | 5 | help: 6 | @echo "clean-build - remove build artifacts" 7 | @echo "clean-pyc - remove Python file artifacts" 8 | @echo "lint - check style with flake8" 9 | @echo "test - run tests quickly with the default Python" 10 | @echo "testall - run tests on every Python version with tox" 11 | @echo "release - package and upload a release" 12 | @echo "dist - package" 13 | 14 | clean: clean-build clean-pyc 15 | 16 | clean-build: 17 | rm -fr build/ 18 | rm -fr dist/ 19 | rm -fr *.egg-info 20 | 21 | clean-pyc: 22 | find . -name '*.pyc' -exec rm -f {} + 23 | find . -name '*.pyo' -exec rm -f {} + 24 | find . -name '*~' -exec rm -f {} + 25 | 26 | lint: 27 | tox -elint 28 | 29 | lint-roll: 30 | isort --recursive py_snappy tests 31 | $(MAKE) lint 32 | 33 | test: 34 | pytest tests 35 | 36 | test-all: 37 | tox 38 | 39 | build-docs: 40 | sphinx-apidoc -o docs/ . setup.py "*conftest*" 41 | $(MAKE) -C docs clean 42 | $(MAKE) -C docs html 43 | $(MAKE) -C docs doctest 44 | 45 | docs: build-docs 46 | open docs/_build/html/index.html 47 | 48 | linux-docs: build-docs 49 | xdg-open docs/_build/html/index.html 50 | 51 | release: clean 52 | git config commit.gpgSign true 53 | bumpversion $(bump) 54 | git push upstream && git push upstream --tags 55 | python setup.py sdist bdist_wheel 56 | twine upload dist/* 57 | git config commit.gpgSign "$(CURRENT_SIGN_SETTING)" 58 | 59 | dist: clean 60 | python setup.py sdist bdist_wheel 61 | ls -l dist 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-snappy 2 | 3 | [![Join the chat at https://gitter.im/ethereum/py-snappy](https://badges.gitter.im/ethereum/py-snappy.svg)](https://gitter.im/ethereum/py-snappy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://circleci.com/gh/ethereum/py-snappy.svg?style=shield)](https://circleci.com/gh/ethereum/py-snappy) 5 | [![PyPI version](https://badge.fury.io/py/py-snappy.svg)](https://badge.fury.io/py/py-snappy) 6 | [![Python versions](https://img.shields.io/pypi/pyversions/py-snappy.svg)](https://pypi.python.org/pypi/py-snappy) 7 | [![Docs build](https://readthedocs.org/projects/py-snappy/badge/?version=latest)](http://py-snappy.readthedocs.io/en/latest/?badge=latest) 8 | 9 | 10 | A pure python implementation of the Snappy compression algorithm 11 | 12 | Read more in the [documentation on ReadTheDocs](https://py-snappy.readthedocs.io/). [View the change log](https://py-snappy.readthedocs.io/en/latest/releases.html). 13 | 14 | ## Quickstart 15 | 16 | ```sh 17 | pip install py-snappy 18 | ``` 19 | 20 | ## Developer Setup 21 | 22 | If you would like to hack on py-snappy, please check out the 23 | [Ethereum Development Tactical Manual](https://github.com/pipermerriam/ethereum-dev-tactical-manual) 24 | for information on how we do: 25 | 26 | - Testing 27 | - Pull Requests 28 | - Code Style 29 | - Documentation 30 | 31 | ### Development Environment Setup 32 | 33 | You can set up your dev environment with: 34 | 35 | ```sh 36 | git clone git@github.com:ethereum/py-snappy.git 37 | cd py-snappy 38 | virtualenv -p python3 venv 39 | . venv/bin/activate 40 | pip install -e .[dev] 41 | ``` 42 | 43 | ### Testing Setup 44 | 45 | During development, you might like to have tests run on every file save. 46 | 47 | Show flake8 errors on file change: 48 | 49 | ```sh 50 | # Test flake8 51 | when-changed -v -s -r -1 py_snappy/ tests/ -c "clear; flake8 py_snappy tests && echo 'flake8 success' || echo 'error'" 52 | ``` 53 | 54 | Run multi-process tests in one command, but without color: 55 | 56 | ```sh 57 | # in the project root: 58 | pytest --numprocesses=4 --looponfail --maxfail=1 59 | # the same thing, succinctly: 60 | pytest -n 4 -f --maxfail=1 61 | ``` 62 | 63 | Run in one thread, with color and desktop notifications: 64 | 65 | ```sh 66 | cd venv 67 | ptw --onfail "notify-send -t 5000 'Test failure ⚠⚠⚠⚠⚠' 'python 3 test on py-snappy failed'" ../tests ../py_snappy 68 | ``` 69 | 70 | ### Release setup 71 | 72 | For Debian-like systems: 73 | ``` 74 | apt install pandoc 75 | ``` 76 | 77 | To release a new version: 78 | 79 | ```sh 80 | make release bump=$$VERSION_PART_TO_BUMP$$ 81 | ``` 82 | 83 | #### How to bumpversion 84 | 85 | The version format for this repo is `{major}.{minor}.{patch}` for stable, and 86 | `{major}.{minor}.{patch}-{stage}.{devnum}` for unstable (`stage` can be alpha or beta). 87 | 88 | To issue the next version in line, specify which part to bump, 89 | like `make release bump=minor` or `make release bump=devnum`. This is typically done from the 90 | master branch, except when releasing a beta (in which case the beta is released from master, 91 | and the previous stable branch is released from said branch). To include changes made with each 92 | release, update "docs/releases.rst" with the changes, and apply commit directly to master 93 | before release. 94 | 95 | If you are in a beta version, `make release bump=stage` will switch to a stable. 96 | 97 | To issue an unstable version when the current version is stable, specify the 98 | new version explicitly, like `make release bump="--new-version 4.0.0-alpha.1 devnum"` 99 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(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/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/web3.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/web3.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/web3" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/web3" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/.suppress-sphinx-build-warning: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/docs/_static/.suppress-sphinx-build-warning -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # py-snappy documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Oct 16 20:43:24 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | #sys.path.insert(0, os.path.abspath('.')) 19 | 20 | import os 21 | 22 | DIR = os.path.dirname('__file__') 23 | with open (os.path.join(DIR, '../setup.py'), 'r') as f: 24 | for line in f: 25 | if 'version=' in line: 26 | setup_version = line.split('\'')[1] 27 | break 28 | 29 | # -- General configuration ------------------------------------------------ 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | #needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 38 | 'sphinx.ext.autodoc', 39 | 'sphinx.ext.doctest', 40 | 'sphinx.ext.intersphinx', 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ['_templates'] 45 | 46 | # The suffix of source filenames. 47 | source_suffix = '.rst' 48 | 49 | # The encoding of source files. 50 | #source_encoding = 'utf-8-sig' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'py-snappy' 57 | copyright = '2018, Jason Carver, Piper Merriam' 58 | 59 | __version__ = setup_version 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '.'.join(__version__.split('.')[:2]) 66 | # The full version, including alpha/beta/rc tags. 67 | release = __version__ 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | #language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | #today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | #today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = [ 82 | '_build', 83 | 'modules.rst', 84 | ] 85 | 86 | # The reST default role (used for this markup: `text`) to use for all 87 | # documents. 88 | #default_role = None 89 | 90 | # If true, '()' will be appended to :func: etc. cross-reference text. 91 | #add_function_parentheses = True 92 | 93 | # If true, the current module name will be prepended to all description 94 | # unit titles (such as .. function::). 95 | #add_module_names = True 96 | 97 | # If true, sectionauthor and moduleauthor directives will be shown in the 98 | # output. They are ignored by default. 99 | #show_authors = False 100 | 101 | # The name of the Pygments (syntax highlighting) style to use. 102 | pygments_style = 'sphinx' 103 | 104 | # A list of ignored prefixes for module index sorting. 105 | #modindex_common_prefix = [] 106 | 107 | # If true, keep warnings as "system message" paragraphs in the built documents. 108 | #keep_warnings = False 109 | 110 | 111 | # -- Options for HTML output ---------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | html_theme = 'sphinx_rtd_theme' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | #html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | 124 | # The name for this set of Sphinx documents. If None, it defaults to 125 | # " v documentation". 126 | #html_title = None 127 | 128 | # A shorter title for the navigation bar. Default is the same as html_title. 129 | #html_short_title = None 130 | 131 | # The name of an image file (relative to this directory) to place at the top 132 | # of the sidebar. 133 | #html_logo = None 134 | 135 | # The name of an image file (within the static path) to use as favicon of the 136 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 137 | # pixels large. 138 | #html_favicon = None 139 | 140 | # Add any paths that contain custom static files (such as style sheets) here, 141 | # relative to this directory. They are copied after the builtin static files, 142 | # so a file named "default.css" will overwrite the builtin "default.css". 143 | html_static_path = ['_static'] 144 | 145 | # Add any extra paths that contain custom files (such as robots.txt or 146 | # .htaccess) here, relative to this directory. These files are copied 147 | # directly to the root of the documentation. 148 | #html_extra_path = [] 149 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 151 | # using the given strftime format. 152 | #html_last_updated_fmt = '%b %d, %Y' 153 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to 155 | # typographically correct entities. 156 | #html_use_smartypants = True 157 | 158 | # Custom sidebar templates, maps document names to template names. 159 | #html_sidebars = {} 160 | 161 | # Additional templates that should be rendered to pages, maps page names to 162 | # template names. 163 | #html_additional_pages = {} 164 | 165 | # If false, no module index is generated. 166 | #html_domain_indices = True 167 | 168 | # If false, no index is generated. 169 | #html_use_index = True 170 | 171 | # If true, the index is split into individual pages for each letter. 172 | #html_split_index = False 173 | 174 | # If true, links to the reST sources are added to the pages. 175 | #html_show_sourcelink = True 176 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 178 | #html_show_sphinx = True 179 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages will 184 | # contain a tag referring to it. The value of this option must be the 185 | # base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Output file base name for HTML help builder. 192 | htmlhelp_basename = 'py_snappydoc' 193 | 194 | 195 | # -- Options for LaTeX output --------------------------------------------- 196 | 197 | latex_elements = { 198 | # The paper size ('letterpaper' or 'a4paper'). 199 | #'papersize': 'letterpaper', 200 | 201 | # The font size ('10pt', '11pt' or '12pt'). 202 | #'pointsize': '10pt', 203 | 204 | # Additional stuff for the LaTeX preamble. 205 | #'preamble': '', 206 | } 207 | 208 | # Grouping the document tree into LaTeX files. List of tuples 209 | # (source start file, target name, title, 210 | # author, documentclass [howto, manual, or own class]). 211 | latex_documents = [ 212 | ('index', 'py_snappy.tex', 'py-snappy Documentation', 213 | 'Jason Carver', 'manual'), 214 | ] 215 | 216 | # The name of an image file (relative to this directory) to place at the top of 217 | # the title page. 218 | #latex_logo = None 219 | 220 | # For "manual" documents, if this is true, then toplevel headings are parts, 221 | # not chapters. 222 | #latex_use_parts = False 223 | 224 | # If true, show page references after internal links. 225 | #latex_show_pagerefs = False 226 | 227 | # If true, show URL addresses after external links. 228 | #latex_show_urls = False 229 | 230 | # Documents to append as an appendix to all manuals. 231 | #latex_appendices = [] 232 | 233 | # If false, no module index is generated. 234 | #latex_domain_indices = True 235 | 236 | 237 | # -- Options for manual page output --------------------------------------- 238 | 239 | # One entry per manual page. List of tuples 240 | # (source start file, name, description, authors, manual section). 241 | man_pages = [ 242 | ('index', 'py_snappy', 'py-snappy Documentation', 243 | ['Jason Carver'], 1) 244 | ] 245 | 246 | # If true, show URL addresses after external links. 247 | #man_show_urls = False 248 | 249 | 250 | # -- Options for Texinfo output ------------------------------------------- 251 | 252 | # Grouping the document tree into Texinfo files. List of tuples 253 | # (source start file, target name, title, author, 254 | # dir menu entry, description, category) 255 | texinfo_documents = [ 256 | ('index', 'py-snappy', 'py-snappy Documentation', 257 | 'Jason Carver', 'py-snappy', 'A pure python implementation of the Snappy compression algorithm', 258 | 'Miscellaneous'), 259 | ] 260 | 261 | # Documents to append as an appendix to all manuals. 262 | #texinfo_appendices = [] 263 | 264 | # If false, no module index is generated. 265 | #texinfo_domain_indices = True 266 | 267 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 268 | #texinfo_show_urls = 'footnote' 269 | 270 | # If true, do not generate a @detailmenu in the "Top" node's menu. 271 | #texinfo_no_detailmenu = False 272 | 273 | # -- Intersphinx configuration ------------------------------------------------ 274 | 275 | intersphinx_mapping = { 276 | 'python': ('https://docs.python.org/3.5', None), 277 | } 278 | 279 | # -- Doctest configuration ---------------------------------------- 280 | 281 | import doctest 282 | 283 | doctest_default_flags = (0 284 | | doctest.DONT_ACCEPT_TRUE_FOR_1 285 | | doctest.ELLIPSIS 286 | | doctest.IGNORE_EXCEPTION_DETAIL 287 | | doctest.NORMALIZE_WHITESPACE 288 | ) 289 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | py-snappy 2 | ============================== 3 | 4 | A pure python implementation of the Snappy compression algorithm 5 | 6 | Contents 7 | -------- 8 | 9 | .. toctree:: 10 | :maxdepth: 3 11 | 12 | py_snappy 13 | releases 14 | 15 | 16 | Indices and tables 17 | ------------------ 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | -------------------------------------------------------------------------------- /docs/releases.rst: -------------------------------------------------------------------------------- 1 | Release Notes 2 | ============= 3 | 4 | v0.1.0-alpha.1 5 | -------------- 6 | 7 | - Launched repository, claimed names for pip, RTD, github, etc 8 | -------------------------------------------------------------------------------- /py_snappy/__init__.py: -------------------------------------------------------------------------------- 1 | from .exceptions import BaseSnappyError, CorruptError, TooLargeError # noqa: F401 2 | from .main import compress, decompress # noqa: F401 3 | -------------------------------------------------------------------------------- /py_snappy/constants.py: -------------------------------------------------------------------------------- 1 | TAG_LITERAL = 0x00 2 | TAG_COPY1 = 0x01 3 | TAG_COPY2 = 0x02 4 | TAG_COPY4 = 0x03 5 | 6 | # https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt says 7 | # that "the uncompressed data in a chunk must be no longer than 65536 bytes". 8 | # TODO: enforce this. 9 | MAX_UNCOMPRESSED_CHUNK_LEN = 65536 10 | -------------------------------------------------------------------------------- /py_snappy/exceptions.py: -------------------------------------------------------------------------------- 1 | class BaseSnappyError(Exception): 2 | """ 3 | Base error class for snappy module. 4 | """ 5 | 6 | 7 | class CorruptError(BaseSnappyError): 8 | """ 9 | Corrupt input. 10 | """ 11 | 12 | 13 | class TooLargeError(BaseSnappyError): 14 | """ 15 | Decoded block is too large. 16 | """ 17 | -------------------------------------------------------------------------------- /py_snappy/main.py: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/StalkR/misc/commit/ba67f5e94d1b1c2cd550cf310b716c0a8101d7a0#diff-78a5f46979e1e7a85d116872e2c865d4 # noqa: E501 2 | import functools 3 | import itertools 4 | from typing import Any, Callable, Iterable, Tuple, TypeVar 5 | 6 | from .constants import TAG_LITERAL, TAG_COPY1, TAG_COPY2, TAG_COPY4 7 | from .exceptions import BaseSnappyError, CorruptError, TooLargeError 8 | 9 | 10 | # Each encoded block begins with the varint-encoded length of the decoded data, 11 | # followed by a sequence of chunks. Chunks begin and end on byte boundaries. 12 | # The first byte of each chunk is broken into its 2 least and 6 most 13 | # significant bits called l and m: l ranges in [0, 4) and m ranges in [0, 64). 14 | # l is the chunk tag. Zero means a literal tag. All other values mean a copy 15 | # tag. 16 | # 17 | # For literal tags: 18 | # - If m < 60, the next 1 + m bytes are literal bytes. 19 | # - Otherwise, let n be the little-endian unsigned integer denoted by the 20 | # next m - 59 bytes. The next 1 + n bytes after that are literal bytes. 21 | # 22 | # For copy tags, length bytes are copied from offset bytes ago, in the style of 23 | # Lempel-Ziv compression algorithms. In particular: 24 | # - For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12). 25 | # The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 26 | # 8-10 of the offset. The next byte is bits 0-7 of the offset. 27 | # - For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65). 28 | # The length is 1 + m. The offset is the little-endian unsigned integer 29 | # denoted by the next 2 bytes. 30 | # - For l == 3, this tag is a legacy format that is no longer supported. 31 | 32 | 33 | def uint8(n: int) -> int: 34 | return n & ((1 << 8) - 1) 35 | 36 | 37 | def uint32(n: int) -> int: 38 | return n & ((1 << 32) - 1) 39 | 40 | 41 | def uint64(n: int) -> int: 42 | return n & ((1 << 64) - 1) 43 | 44 | 45 | def uvarint(buf: bytes) -> Tuple[int, int]: 46 | """ 47 | uvarint decodes a uint64 from buf and returns that value and the number of 48 | bytes read (> 0). If an error occurred, the value is 0 and the number of 49 | bytes n is <= 0 meaning: 50 | n == 0: buf too small 51 | n < 0: value larger than 64 bits (overflow) 52 | and -n is the number of bytes read""" 53 | value, num_bytes_read = 0, 0 54 | for buf_pos, current_byte in enumerate(buf): 55 | if current_byte < 0x80: 56 | if buf_pos > 9 or (buf_pos == 9 and current_byte > 1): 57 | return 0, -1 * (buf_pos + 1) # overflow 58 | return value | uint64(current_byte) << num_bytes_read, buf_pos + 1 59 | value |= uint64(current_byte & 0x7F) << num_bytes_read 60 | num_bytes_read += 7 61 | return 0, 0 62 | 63 | 64 | TReturn = TypeVar("TReturn") 65 | 66 | 67 | def bytes_gen(fn: Callable[..., Iterable[int]]) -> Callable[..., bytes]: 68 | @functools.wraps(fn) 69 | def inner(*args: Any, **kwargs: Any) -> bytes: 70 | return bytes(fn(*args, **kwargs)) 71 | 72 | return inner 73 | 74 | 75 | def tuple_gen( 76 | fn: Callable[..., Iterable[TReturn]] 77 | ) -> Callable[..., Tuple[TReturn, ...]]: 78 | @functools.wraps(fn) 79 | def inner(*args: Any, **kwargs: Any) -> Iterable[TReturn]: 80 | return tuple(fn(*args, **kwargs)) 81 | 82 | return inner 83 | 84 | 85 | @bytes_gen 86 | def putuvarint(x: int) -> Iterable[int]: 87 | """ 88 | putuvarint encodes a uint64. 89 | """ 90 | while x >= 0x80: 91 | yield uint8(x) | 0x80 92 | x >>= 7 93 | yield x 94 | 95 | 96 | def extract_meta(src: bytes) -> Tuple[int, int]: 97 | """ 98 | Return a 2-tuple: 99 | 100 | - the length of the decoded block 101 | - the number of bytes that the length header occupied. 102 | """ 103 | value, num_bytes = uvarint(src) 104 | if num_bytes <= 0 or value > 0xFFFFFFFF: 105 | raise CorruptError 106 | if value > 0x7FFFFFFF: 107 | raise TooLargeError 108 | return value, num_bytes 109 | 110 | 111 | def decompress(buf: bytes) -> bytes: 112 | """ 113 | decompress returns the decompressed form of buf. 114 | """ 115 | block_length, length_header_size = extract_meta(buf) 116 | src = tuple(c for c in buf) 117 | src_len = len(src) 118 | dst = [0] * block_length 119 | d, offset, length = 0, 0, 0 120 | 121 | while length_header_size < src_len: 122 | elem_type = src[length_header_size] & 0x03 123 | if elem_type == TAG_LITERAL: 124 | literal_length = src[length_header_size] >> 2 125 | 126 | if literal_length < 60: 127 | length_header_size += 1 128 | elif literal_length == 60: 129 | length_header_size += 2 130 | if length_header_size > src_len: 131 | raise CorruptError 132 | literal_length = src[length_header_size - 1] 133 | elif literal_length == 61: 134 | length_header_size += 3 135 | if length_header_size > src_len: 136 | raise CorruptError 137 | literal_length = src[length_header_size - 2] | ( 138 | src[length_header_size - 1] << 8 139 | ) 140 | elif literal_length == 62: 141 | length_header_size += 4 142 | if length_header_size > src_len: 143 | raise CorruptError 144 | literal_length = ( 145 | src[length_header_size - 3] 146 | | (src[length_header_size - 2] << 8) # noqa: W503 147 | | (src[length_header_size - 1] << 16) # noqa: W503 148 | ) 149 | elif literal_length == 63: 150 | length_header_size += 5 151 | if length_header_size > src_len: 152 | raise CorruptError 153 | 154 | literal_length = ( 155 | src[length_header_size - 4] 156 | | (src[length_header_size - 3] << 8) # noqa: W503 157 | | (src[length_header_size - 2] << 16) # noqa: W503 158 | | (src[length_header_size - 1] << 24) # noqa: W503 159 | ) 160 | 161 | length = literal_length + 1 162 | 163 | if length <= 0: 164 | raise BaseSnappyError("Unsupported literal length") 165 | if length > len(dst) - d or length > src_len - length_header_size: 166 | raise CorruptError 167 | 168 | dst = list( 169 | itertools.chain( # noqa: E203 170 | dst[:d], 171 | src[length_header_size : length_header_size + length], # noqa: E203 172 | dst[d + length :], # noqa: E203 173 | ) 174 | ) 175 | d += length 176 | length_header_size += length 177 | continue 178 | 179 | elif elem_type == TAG_COPY1: 180 | length_header_size += 2 181 | if length_header_size > src_len: 182 | raise CorruptError 183 | length = 4 + ((src[length_header_size - 2] >> 2) & 0x7) 184 | offset = ((src[length_header_size - 2] & 0xE0) << 3) | src[ 185 | length_header_size - 1 186 | ] 187 | 188 | elif elem_type == TAG_COPY2: 189 | length_header_size += 3 190 | if length_header_size > src_len: 191 | raise CorruptError 192 | length = 1 + (src[length_header_size - 3] >> 2) 193 | offset = src[length_header_size - 2] | (src[length_header_size - 1] << 8) 194 | 195 | elif elem_type == TAG_COPY4: 196 | raise BaseSnappyError("Unsupported COPY_4 tag") 197 | 198 | end = d + length 199 | if offset > d or end > len(dst): 200 | raise CorruptError 201 | while d < end: 202 | dst[d] = dst[d - offset] 203 | d += 1 204 | 205 | if d != block_length: 206 | raise CorruptError 207 | 208 | return bytes(dst[:d]) 209 | 210 | 211 | MAX_OFFSET = 1 << 15 212 | 213 | C240 = 60 << 2 214 | C244 = 61 << 2 215 | C248 = 62 << 2 216 | C252 = 63 << 2 217 | C65536 = 1 << 16 218 | C4294967296 = 1 << 32 219 | 220 | 221 | @tuple_gen 222 | def emit_literal(lit: bytes) -> Iterable[int]: 223 | """emit_literal returns a literal chunk.""" 224 | n = len(lit) - 1 225 | 226 | if n < 60: 227 | yield (uint8(n) << 2) | TAG_LITERAL 228 | elif n < C240: 229 | yield C240 | TAG_LITERAL 230 | yield uint8(n) 231 | elif n < C244: 232 | yield C244 | TAG_LITERAL 233 | yield uint8(n) 234 | yield uint8(n >> 8) 235 | elif n < C65536: 236 | yield C248 | TAG_LITERAL 237 | yield uint8(n) 238 | yield uint8(n >> 8) 239 | yield uint8(n >> 16) 240 | elif uint64(n) < C4294967296: 241 | yield C252 | TAG_LITERAL 242 | yield uint8(n) 243 | yield uint8(n >> 8) 244 | yield uint8(n >> 16) 245 | yield uint8(n >> 24) 246 | else: 247 | raise BaseSnappyError("Source buffer is too long") 248 | 249 | yield from lit 250 | 251 | 252 | C8 = 1 << 3 253 | C64 = 1 << 6 254 | C256 = 1 << 8 255 | C2048 = 1 << 11 256 | 257 | 258 | @tuple_gen 259 | def emit_copy(offset: int, length: int) -> Iterable[int]: 260 | """emit_copy writes a copy chunk and returns the number of bytes written.""" 261 | while length > 0: 262 | x = length - 4 263 | if 0 <= x and x < C8 and offset < C2048: 264 | yield ((uint8(offset >> 8) & 0x07) << 5) | (uint8(x) << 2) | TAG_COPY1 265 | yield uint8(offset) 266 | break 267 | 268 | x = length 269 | if x > C64: 270 | x = C64 271 | yield (uint8(x - 1) << 2) | TAG_COPY2 272 | yield uint8(offset) 273 | yield uint8(offset >> 8) 274 | length -= x 275 | 276 | 277 | C24 = 32 - 8 278 | MAX_TABLE_SIZE = 1 << 14 279 | 280 | 281 | @bytes_gen 282 | def compress(buf: bytes) -> Iterable[int]: 283 | """compress returns the compressed form of buf.""" 284 | src = tuple(buf) 285 | src_len = len(src) 286 | 287 | # The block starts with the varint-encoded length of the decompressed bytes. 288 | yield from (c for c in putuvarint(src_len)) 289 | 290 | # Return early if src is short. 291 | if src_len <= 4: 292 | if src_len != 0: 293 | yield from emit_literal(src) 294 | return 295 | 296 | # Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive. 297 | shift, table_size = C24, C256 298 | while table_size < MAX_TABLE_SIZE and table_size < src_len: 299 | shift -= 1 300 | table_size *= 2 301 | table = [0] * MAX_TABLE_SIZE 302 | 303 | # Iterate over the source bytes. 304 | iter_pos = 0 # The iterator position. 305 | last_matching_hash_pos = 0 # The last position with the same hash as s. 306 | literal_start_pos = 0 # The start position of any pending literal bytes. 307 | 308 | while iter_pos + 3 < src_len: 309 | # Update the hash table. 310 | b0, b1, b2, b3 = src[iter_pos : iter_pos + 4] # noqa: E203 311 | hash_code = ( 312 | uint32(b0) | (uint32(b1) << 8) | (uint32(b2) << 16) | (uint32(b3) << 24) 313 | ) 314 | hash_bucket = uint32(hash_code * 0x1E35A7BD) >> shift 315 | 316 | # We need to to store values in [-1, inf) in table. To save 317 | # some initialization time, (re)use the table's zero value 318 | # and shift the values against this zero: add 1 on writes, 319 | # subtract 1 on reads. 320 | last_matching_hash_pos = table[hash_bucket] - 1 321 | table[hash_bucket] = iter_pos 322 | 323 | if ( 324 | last_matching_hash_pos < 0 325 | or iter_pos - last_matching_hash_pos >= MAX_OFFSET # noqa: W503 326 | or b0 != src[last_matching_hash_pos] # noqa: W503 327 | or b1 != src[last_matching_hash_pos + 1] # noqa: W503 328 | or b2 != src[last_matching_hash_pos + 2] # noqa: W503 329 | or b3 != src[last_matching_hash_pos + 3] # noqa: W503 330 | ): 331 | # If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte. 332 | iter_pos += 1 333 | continue 334 | 335 | elif literal_start_pos != iter_pos: 336 | # Otherwise, we have a match. First, emit any pending literal bytes. 337 | yield from emit_literal(src[literal_start_pos:iter_pos]) 338 | 339 | # Extend the match to be as long as possible. 340 | s0 = iter_pos 341 | iter_pos = iter_pos + 4 342 | last_matching_hash_pos = last_matching_hash_pos + 4 343 | 344 | while iter_pos < src_len and src[iter_pos] == src[last_matching_hash_pos]: 345 | iter_pos += 1 346 | last_matching_hash_pos += 1 347 | 348 | # Emit the copied bytes. 349 | yield from emit_copy(iter_pos - last_matching_hash_pos, iter_pos - s0) 350 | literal_start_pos = iter_pos 351 | 352 | # Emit any final pending literal bytes and return. 353 | if literal_start_pos != src_len: 354 | yield from emit_literal(src[literal_start_pos:]) 355 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts= -v --showlocals --durations 10 3 | python_paths= . 4 | xfail_strict=true 5 | 6 | [pytest-watch] 7 | runner= pytest --failed-first --maxfail=1 --no-success-flaky-report 8 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | py-snappy[doc] 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import ( 4 | setup, 5 | find_packages, 6 | ) 7 | 8 | extras_require = { 9 | 'test': [ 10 | "pytest>=3.6.0", 11 | "pytest-xdist", 12 | "tox>=2.9.1,<3", 13 | "hypothesis==3.74.3", 14 | "python-snappy>=0.5.3,<1", 15 | ], 16 | 'lint': [ 17 | "flake8==3.4.1", 18 | "black==18.9b0", 19 | "mypy==0.630", 20 | ], 21 | 'doc': [ 22 | "Sphinx>=1.6.5,<2", 23 | "sphinx_rtd_theme>=0.1.9", 24 | ], 25 | 'dev': [ 26 | "bumpversion>=0.5.3,<1", 27 | "pytest-watch>=4.1.0,<5", 28 | "wheel", 29 | "twine", 30 | "ipython", 31 | ], 32 | } 33 | 34 | extras_require['dev'] = ( 35 | extras_require['dev'] + 36 | extras_require['test'] + 37 | extras_require['lint'] + 38 | extras_require['doc'] 39 | ) 40 | 41 | setup( 42 | name='py-snappy', 43 | # *IMPORTANT*: Don't manually change the version here. Use `make bump`, as described in readme 44 | version='0.1.0-alpha.1', 45 | description="""py-snappy: A pure python implementation of the Snappy compression algorithm""", 46 | long_description_markdown_filename='README.md', 47 | author='Jason Carver', 48 | author_email='ethcalibur+pip@gmail.com', 49 | url='https://github.com/ethereum/py-snappy', 50 | include_package_data=True, 51 | install_requires=[], 52 | setup_requires=['setuptools-markdown'], 53 | python_requires='>=3.6, <4', 54 | extras_require=extras_require, 55 | py_modules=['py_snappy'], 56 | license="MIT", 57 | zip_safe=False, 58 | keywords='ethereum', 59 | packages=find_packages(exclude=["tests", "tests.*"]), 60 | classifiers=[ 61 | 'Development Status :: 3 - Alpha', 62 | 'Intended Audience :: Developers', 63 | 'License :: OSI Approved :: MIT License', 64 | 'Natural Language :: English', 65 | 'Programming Language :: Python :: 3', 66 | 'Programming Language :: Python :: 3.6', 67 | 'Programming Language :: Python :: 3.7', 68 | 'Programming Language :: Python :: Implementation :: PyPy', 69 | ], 70 | ) 71 | -------------------------------------------------------------------------------- /tests/core/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/tests/core/conftest.py -------------------------------------------------------------------------------- /tests/core/strategies.py: -------------------------------------------------------------------------------- 1 | from hypothesis import strategies as st 2 | 3 | 4 | MEGABYTE = 1000000 5 | KILOBYTE = 1000 6 | 7 | random_bytes_large_st = st.binary(min_size=0, max_size=2 * MEGABYTE) 8 | random_bytes_small_st = st.binary(min_size=0, max_size=KILOBYTE) 9 | 10 | contiguous_bytes_st = st.tuples( 11 | st.binary(min_size=1, max_size=1), st.integers(min_value=2, max_value=1024) 12 | ).map(lambda v: v[0] * v[1]) 13 | 14 | pattern_bytes_st = st.tuples( 15 | st.binary(min_size=1, max_size=128), st.integers(min_value=2, max_value=32) 16 | ).map(lambda v: v[0] * v[1]) 17 | 18 | random_bytes_with_pattern_blocks_st = st.lists( 19 | st.one_of(random_bytes_small_st, contiguous_bytes_st, pattern_bytes_st), max_size=20 20 | ).map(lambda v: b"".join(v)) 21 | 22 | 23 | random_test_vectors_large_st = st.one_of( 24 | random_bytes_large_st, random_bytes_with_pattern_blocks_st 25 | ) 26 | 27 | 28 | random_test_vectors_small_st = st.one_of( 29 | random_bytes_small_st, random_bytes_with_pattern_blocks_st 30 | ) 31 | -------------------------------------------------------------------------------- /tests/core/test_empty_bytestring.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from py_snappy import compress, decompress, CorruptError 4 | 5 | 6 | def test_compress_empty_string(): 7 | assert compress(b"") == b"\x00" 8 | 9 | 10 | def test_decompress_empty_string(): 11 | with pytest.raises(CorruptError): 12 | decompress(b"") 13 | -------------------------------------------------------------------------------- /tests/core/test_import.py: -------------------------------------------------------------------------------- 1 | def test_import(): 2 | import py_snappy # noqa: F401 3 | -------------------------------------------------------------------------------- /tests/core/test_libsnappy_compat.py: -------------------------------------------------------------------------------- 1 | from hypothesis import given, settings 2 | 3 | from py_snappy import compress, decompress, BaseSnappyError 4 | from snappy import ( 5 | compress as libsnappy_compress, 6 | decompress as libsnappy_decompress, 7 | UncompressError, 8 | ) 9 | 10 | try: 11 | from snappy._snappy import CompressedLengthError 12 | except ImportError: 13 | CompressedLengthError = None 14 | 15 | 16 | from tests.core.strategies import ( 17 | random_test_vectors_large_st, 18 | random_test_vectors_small_st, 19 | ) 20 | 21 | 22 | # 23 | # Round trip value -> compress() -> decompress() 24 | # 25 | @given(value=random_test_vectors_large_st) 26 | @settings(max_examples=1000) 27 | def test_local_decompress_libsnappy_compressed(value): 28 | intermediate = libsnappy_compress(value) 29 | result = decompress(intermediate) 30 | assert value == result 31 | 32 | 33 | @given(value=random_test_vectors_large_st) 34 | @settings(max_examples=1000) 35 | def test_libsnappy_decompress_local_compressed(value): 36 | intermediate = compress(value) 37 | result = libsnappy_decompress(intermediate) 38 | assert value == result 39 | 40 | 41 | LIB_SNAPPY_ERRORS = (CompressedLengthError, UncompressError) 42 | PY_SNAPPY_ERRORS = (BaseSnappyError,) 43 | 44 | 45 | def pass_fail(v): 46 | if v: 47 | return "pass" 48 | else: 49 | return "fail" 50 | 51 | 52 | # 53 | # Error cases 54 | # 55 | @given(value=random_test_vectors_small_st) 56 | @settings(max_examples=100, deadline=None) # takes a long time. 57 | def test_decompress_error_parity(value): 58 | try: 59 | py_result = decompress(value) 60 | except PY_SNAPPY_ERRORS: 61 | py_snappy_error = True 62 | else: 63 | py_snappy_error = False 64 | 65 | try: 66 | lib_result = libsnappy_decompress(value) 67 | except LIB_SNAPPY_ERRORS: 68 | lib_snappy_error = True 69 | else: 70 | lib_snappy_error = False 71 | 72 | if py_snappy_error and lib_snappy_error: 73 | pass 74 | elif not py_snappy_error and not lib_snappy_error: 75 | assert lib_result == py_result 76 | else: 77 | raise AssertionError( 78 | f"behavioral mismatch: py_snappy: {pass_fail(py_snappy_error)} lib-snappy: {pass_fail(lib_snappy_error)}" # noqa: E501 79 | ) 80 | -------------------------------------------------------------------------------- /tests/core/test_official_test_vectors.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | 5 | import py_snappy 6 | from py_snappy import BaseSnappyError, compress, decompress 7 | from snappy import ( 8 | compress as libsnappy_compress, 9 | decompress as libsnappy_decompress, 10 | UncompressError, 11 | ) 12 | 13 | 14 | BASE_DIR = Path(py_snappy.__file__).resolve().parent.parent 15 | FIXTURES_DIR = BASE_DIR / "tests" / "fixtures" 16 | 17 | 18 | def load_fixture(fixture_name): 19 | fixture_path = FIXTURES_DIR / fixture_name 20 | assert fixture_path.exists() 21 | assert fixture_path.is_file() 22 | return fixture_path.read_bytes() 23 | 24 | 25 | FIXTURES_TO_COMPRESS = ( 26 | "alice29.txt", 27 | "asyoulik.txt", 28 | "fireworks.jpeg", 29 | "geo.protodata", 30 | "html", 31 | "html_x_4", 32 | "kppkn.gtb", 33 | "lcet10.txt", 34 | "paper-100k.pdf", 35 | "plrabn12.txt", 36 | "urls.10K", 37 | ) 38 | 39 | 40 | @pytest.mark.parametrize("fixture_name", FIXTURES_TO_COMPRESS) 41 | def test_compression_round_trip_of_official_test_fixtures(fixture_name): 42 | fixture_data = load_fixture(fixture_name) 43 | intermediate = compress(fixture_data) 44 | actual = decompress(intermediate) 45 | assert fixture_data == actual 46 | 47 | 48 | @pytest.mark.parametrize("fixture_name", FIXTURES_TO_COMPRESS) 49 | def test_decompress_libsnappy_compressed_test_fixture(fixture_name): 50 | fixture_data = load_fixture(fixture_name) 51 | intermediate = libsnappy_compress(fixture_data) 52 | actual = decompress(intermediate) 53 | assert fixture_data == actual 54 | 55 | 56 | @pytest.mark.parametrize("fixture_name", FIXTURES_TO_COMPRESS) 57 | def test_libsnapp_decompress_compressed_test_fixture(fixture_name): 58 | fixture_data = load_fixture(fixture_name) 59 | intermediate = compress(fixture_data) 60 | actual = libsnappy_decompress(intermediate) 61 | assert fixture_data == actual 62 | 63 | 64 | FIXTURES_TO_DECOMPRESS = ("baddata1.snappy", "baddata2.snappy", "baddata3.snappy") 65 | 66 | 67 | @pytest.mark.parametrize("fixture_name", FIXTURES_TO_DECOMPRESS) 68 | def test_decompression_of_official_corrupt_fixtures(fixture_name): 69 | fixture_data = load_fixture(fixture_name) 70 | with pytest.raises(BaseSnappyError): 71 | decompress(fixture_data) 72 | 73 | # now ensure that the canonical implementation errors too 74 | with pytest.raises(UncompressError): 75 | libsnappy_decompress(fixture_data) 76 | -------------------------------------------------------------------------------- /tests/core/test_round_trip.py: -------------------------------------------------------------------------------- 1 | from hypothesis import given, settings 2 | 3 | from py_snappy import compress, decompress 4 | 5 | from tests.core.strategies import random_test_vectors_large_st 6 | 7 | 8 | @given(value=random_test_vectors_large_st) 9 | @settings(max_examples=10000) 10 | def test_round_trip(value): 11 | intermediate = compress(value) 12 | result = decompress(intermediate) 13 | assert value == result 14 | -------------------------------------------------------------------------------- /tests/fixtures/baddata1.snappy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/tests/fixtures/baddata1.snappy -------------------------------------------------------------------------------- /tests/fixtures/baddata2.snappy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/tests/fixtures/baddata2.snappy -------------------------------------------------------------------------------- /tests/fixtures/baddata3.snappy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/tests/fixtures/baddata3.snappy -------------------------------------------------------------------------------- /tests/fixtures/fireworks.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/tests/fixtures/fireworks.jpeg -------------------------------------------------------------------------------- /tests/fixtures/geo.protodata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethereum/py-snappy/f91082fa1498ed919ef26bd853a4552675c9665a/tests/fixtures/geo.protodata -------------------------------------------------------------------------------- /tests/fixtures/html: -------------------------------------------------------------------------------- 1 | content: @ 1099872000000000: 'HTTP/1.1 200 OK\r\nX-Google-Crawl-Date: Mon, 08 Nov 2004 17:22:09 GMT\r\nContent-Type: text/html\r\nConnection: close\r\nX-Powered-By: PHP/4.3.8\r\nServer: Apache/1.3.31 (Unix) mod_gzip/1.3.19.1a PHP/4.3.8\r\nDate: Mon, 08 Nov 2004 17:19:07 GMT\r\n\r\n \r\n\r\n\r\n\r\n\r\n\nMicro Achat : Ordinateurs, PDA - Toute l\'informatique avec 01Informatique, L\'Ordinateur Individuel, Micro Hebdo, D\351cision Informatique et 01R\351seaux\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n

Derni\350re mise \340 jour de cette page : lundi 8 novembre 2004  |  16:45
\r\n \r\n\r\n\r\n\r\n\t\r\n\r\n\t\t\r\n\r\n\t\r\n\r\n\t\r\n\t\t\r\n\r\n\r\n\t\t\r\n\r\n\t\t\r\n\t\r\n\t\r\n\t\t\t

\r\n\r\n\r\n\r\n


\r\n\r\n\r\n\r\n
\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\"\"
Imagerie 
\"\"\n\t\t\t\t\t\t\t\tLG L1720B\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\340 partir de\n\t\t\t\t\t\t\t\t\t
332.89 €
\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
Ordinateurs 
\"\"\n\t\t\t\t\t\t\t\tAcer Veriton 7600G\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\340 partir de\n\t\t\t\t\t\t\t\t\t
705 €
\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
Ordinateurs 
\"\"\n\t\t\t\t\t\t\t\tShuttle SN95G5\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\340 partir de\n\t\t\t\t\t\t\t\t\t
375 €
\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
Composants 
\"\"\n\t\t\t\t\t\t\t\tAsus A7N8X-E Deluxe\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\340 partir de\n\t\t\t\t\t\t\t\t\t
91.99 €
\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
Composants 
\"\"\n\t\t\t\t\t\t\t\tThermalright SP-94\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\340 partir de\n\t\t\t\t\t\t\t\t\t
49 €
\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\"\"
1 \">\"PC Look
2 \">\"Atelier Informatique
3 \">\"Zanax Multim\351dia
4 \">\"MISTEROOPS
5 \">\"168 Golden Avenue
6 \">\"microchoix
7 \">\"e-Soph
8 \">\"PC Price Club
9 \">\"PC 77
10 \">\"Web In Informatique
\n\t\t\t\t
\n\t\t\t\t
\r\n \r\n\r\n\r\n\r\n\r\n\r\n
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t
\n\n\n\n\n\n\n\n\n\n\n\n\n
\n\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\r\n\r\n\r\n\t\t\r\n\t\t\t\r\n\t\t\r\n

\r\n\t\t\t

\r\n\t\t\t

\r\n\t\t\t
\r\n\t\t\t
\r\n\r\n\r\n \r\n \r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n
\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t

CD et DVD bient\364t insensibles aux rayures
OpenOffice gagne son service
La messagerie en cinq minutes selon Ipswitch
> toutes les news


\r\n\t\t
\r\n \r\n\r\n\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n
\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t

Recevez chaque jour l\'actualit\351 des produits et des promos
\r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t


\r\n\t\t\t
\r\n\t\t\t\t
\r\n\t\t
\r\n\r\n\r\n\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n
\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t \r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t

Entreprise
\r\n\t\t\t\tQuand le billet papier s\'envole vers la d\351mat\351rialisation


Trucs et astuces
\r\n\t\t\t\tD\351pannez Windows XP


Conso
\r\n\t\t\t\tVos photos sur papier imprimante ou labo ?


Produits & Tests
\r\n\t\t\t\t5 programmes d\222encodage vid\351o gratuits


\r\n\t\t
\r\n\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n
\r\n
\r\n
\r\n\t\t\r\n\t\t

\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n
\r\n\r\n\r\n\t\r\n\t\t\r\n\t\r\n
\r\n\t
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n \r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n
\r\n\r\n\r\n\t\r\n\r\n\r\n\r\n\t\t\t\r\n\r\n\r\n\r\n\t\t\t\t\r\n
\r\n
\r\nPortable
\r\nUn nouvel ultra portable r\351alis\351 par Nec
\r\n\r\n\t\t\t\t\t \t \t\t\t\t\t\t\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t \t\r\nLe Versa S940 a un format r\351duit, mais ses performances sont \340 la hauteur.
\r\n\340 partir de 1663 \200\r\n
\r\n
\r\nPortable
\r\nAsus pr\351sente trois petits nouveaux dans la gamme A3N
\r\n\r\n\t\t\t\t\t \t \t\t\t\t\t\t\t\t\t\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t \t\r\nCes trois portables Centrino int\350grent, entre autres, une webcam et un contr\364leur Wi-Fi.
\r\n\340 partir de 1346 \200\r\n
\r\n
\r\n\t\r\n\t\r\n\t\r\n \t\r\n\t\r\n\t\r\n \t\r\n\t\r\n\t
\r\n\t\r\n\r\n
\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
BON PLAN
\r\n
\r\n
\r\nLes derni\350res technologies INTEL dans un nouveau design pour ce shuttle haut de gamme, pour un prix abordable.
\r\n

\r\n
\r\n\340 partir de
\r\n
415 \200
\r\n
\r\n
\r\n
publicit\351
\r\n
\r\n\t\r\n\r\n
\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n
\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t

\r\n\t\t\t\t\t\t\t\t\t\t\t\t
\r\n\t\t\t\tDesktops
\r\n\t\t\t\tPortables
\r\n\t\t\t\tMini-PC
\r\n\t\t\t\tPda / Tablets-PC
\r\n\t\t\t\tApple
\r\n\t\t\t\tGPS
\r\n\t\t\t\t
\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t


\r\n\t\t\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t
\r\n
\r\n
\r\n\t\t\r\n\t\t\r\n\r\n\r\n \r\n\r\n\r\n \r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n
\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n

\r\n\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\t\t\r\n\t\r\n\t\r\n\t\t\t\t\r\n\t\r\n\t\r\n\t\t\t\t\r\n\t\r\n\t\r\n\t\t\t\t\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t
\r\n\tPortable Toshiba consacre deux gammes de machines au multim\351dia
\r\n\tEquipement haut de gamme et Windows Media Center sont au menu de ces portables \340 vocation multim\351dia.

\r\n\tOrdinateur Arriv\351e d\'un Power Mac G5 d\'entr\351e de gamme
\r\n\tLa firme \340 la pomme propose une station de travail \351volutive et relativement abordable.

\r\n\tPC Alienware propose deux machines au look \351trange
\r\n\tAurora et Area 51 sont deux gammes d\'ordinateurs enti\350rement configurables.

\r\n\tPortable Trois nouveaux iBook G4 chez Apple
\r\n\tChez Apple, les portables gagnent en vitesse et communiquent sans fil en standard.

\r\n\t\t\t\t> toutes les news\r\n\t\t\t
\r\n
\r\n
\r\n
\r\n\r\n\r\n\r\n\r\n

\r\n\r\n\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
\r\n\t\t\t\t\t \t \t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t \tAsus A3N15-C Pro
\r\n Voici un portable autonome et puissant gr\342ce \340 la technologie Intel Centrino.
\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t
\r\n\t1170 \200
\r\n
\r\n\t\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
\r\n\t\t\t\t\t \t \t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t \tSoltek EQ3702A Miroir
\r\n Ce mini PC est une solution int\351ressante pour les utilisateurs poss\351dant d\351j\340 un \351cran.
\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t
\r\n\t559 \200
\r\n
\r\n\t\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
\r\n\t\t\t\t\t \t \t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t \tIBM ThinkPad R51
\r\n Voici un portable complet et pourtant relativement l\351ger.
\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t
\r\n\t1299 \200
\r\n
\r\n\t\t\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
\r\n\t\t\t\t> toutes les promos\r\n\t\t
\r\n
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n

\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\r\n

\r\n
\r\n\t\t\r\n\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t
\r\n\t\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\t
\r\n\t\t\t\r\n\t\t\t\t\r\n\r\n\r\n\r\n \r\n\r\n\r\n \r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\r\n
\r\n\t\t
\r\n\t\r\n\r\n
\r\n\r\n\t\t\t\t\t \t \t\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t \t\r\n
\r\n
\r\nLes graveurs de DVD
\r\nQuel graveur choisir ? Quel type de format ? Quelle vitesse ? Double couche ou simple couche ? Voici tout ce qu\'il faut savoir pour faire le bon choix.
\r\n\t\t\t\t\t\t
\r\n\t\t
\r\n
\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\r\n
\r\n\t\t
\r\n
\r\n\r\n\r\n\t\r\n\t\t\r\n\r\n\r\n \t\r\n
\r\n\t\t
\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t
\r\n\t\t\t\t

\r\n\t\t\t\tChoisir une r\351gion
\r\n\r\n
Un d\351partement
\r\n\r\n
\r\n
Un arrondissement
\r\n\r\n
\r\n
\r\n\t\t\t\tRecherche directe
\r\n\t\t\t\trechercher une ville
et/ou une boutique
\r\n\t\t\t\t

\r\n\t\t\t\t \r\n\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\r\n\t\t\t\t
Recherche avanc\351e
\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t\t\t
\r\n\t\t
\r\n
\r\n\r\n\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\r\n
\r\n\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t
Bureautique
\r\n\t\t\t\tTraducteur, organiseur...

\r\n\t\t\t\t

Multim\351dia
\r\n\t\t\t\tPhoto, audio, vid\351o...

\r\n\t\t\t\t

Utilitaires
\r\n\t\t\t\tAntivirus, pilotes, gravure...

\r\n\t\t\t\t

Personnaliser son PC
\r\n\t\t\t\tEcrans de veille, th\350mes...

\r\n\t\t\t\t

D\351veloppement
\r\n\t\t\t\tCr\351ation de logiciels, BDD...

\r\n\t\t\t\t

Jeux
\r\n\t\t\t\tAction, simulation...

\r\n\t\t\t\t

Internet
\r\n\t\t\t\tUtilitaires, email, FTP...

\r\n\t\t\t\t

Loisirs
\r\n\t\t\t\tHumour, culture...

\r\n\t\t\t\t
\r\n\t\t
\r\n
\r\n
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\r\n\t\t
\r\n\t\t
\r\n\t\t\r\n\t\t\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\t\t\t\r\n\t\t\t
\r\n\t\t\t
\r\n\r\n\t\t

\r\n\t\t\t\r\n\r\n\r\n\r\n\r\n\r\n
\r\n
\r\n
\r\n\r\n\r\n\r\n