Repository: ziyasal/pyley Branch: master Commit: a2b961abfd5f Files: 18 Total size: 26.5 KB Directory structure: gitextract_tu09thrq/ ├── .appveyor.sh ├── .gitignore ├── .travis.sh ├── .travis.yml ├── MANIFEST.in ├── README.rst ├── UNLICENSE ├── appveyor.yml ├── pyley/ │ ├── __init__.py │ └── pyley.py ├── requirements.testing.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests/ │ ├── __init__.py │ ├── test_cayley_client.py │ └── test_gizmo_query.py └── tox.ini ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appveyor.sh ================================================ appveyor DownloadFile https://github.com/cayleygraph/cayley/releases/download/v0.7.0/cayley_0.7.0_windows_amd64.zip -Timeout 5000 7z x cayley_0.7.0_windows_amd64.zip cayley_0.7.0_windows_amd64 cd /? cd cayley_0.7.0_windows_amd64 cd /? dir /a cayley.exe http --dbpath=30kmoviedata.nq.gz & sleep 2 ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so #PyCharm project folder .idea/ # Distribution / packaging .Python env/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation docs/_build/ ================================================ FILE: .travis.sh ================================================ #!/bin/bash wget https://github.com/cayleygraph/cayley/releases/download/v0.7.0/cayley_0.7.0_linux_amd64.tar.gz tar -xvzf cayley_0.7.0_linux_amd64.tar.gz cd cayley_0.7.0_linux_amd64 ./cayley http --dbpath=30kmoviedata.nq.gz & sleep 2 ================================================ FILE: .travis.yml ================================================ language: python python: - "2.7" - "3.4" - "3.5" - "3.6" - "3.7" - "3.8" before_script: - /bin/bash ./.travis.sh install: - pip install -r requirements.txt - pip install -r requirements.testing.txt - pip install . script: - nosetests --with-coverage --cover-package=pyley after_success: - coveralls ================================================ FILE: MANIFEST.in ================================================ include README.md ================================================ FILE: README.rst ================================================ .. image:: https://github.com/ziyasal/pyley/raw/master/pyley.png?raw=true pyley ===== .. image:: https://img.shields.io/pypi/v/pyley.svg :target: https://pypi.org/project/pyley .. image:: https://img.shields.io/pypi/pyversions/pyley.svg :target: https://pypi.org/project/pyley .. image:: https://travis-ci.org/ziyasal/pyley.svg?branch=master :target: https://travis-ci.org/ziyasal/pyley .. image:: https://coveralls.io/repos/ziyasal/pyley/badge.svg?branch=master&service=github :target: https://coveralls.io/github/ziyasal/pyley?branch=master `Python `_ client for an open-source graph database **Cayley** ``_. Cayley is an open-source graph inspired by the graph database behind `Freebase `_ and Google's `Knowledge Graph `_. Its goal is to be a part of the developer's toolbox where `Linked Data `_ and graph-shaped data (semantic webs, social networks, etc) in general are concerned. Install via pip --------------- You can install pyley using:: $ pip install pyley Sample ------ **Import pyley:** .. code-block:: python from pyley import CayleyClient, GraphObject # Create cayley client # this creates client with default parameters `http://localhost:64210/api/v1/query/gizmo` client = CayleyClient() # or specify `url` and `version` parameters client = CayleyClient("http://localhost:64210", "v1") g = GraphObject() # Query all vertices in the graph, limit to the first 5 vertices found. g.Vertex().GetLimit(5) # Start with only one vertex, the literal name "Humphrey Bogart", and retrieve all of them. query = g.Vertex("Humphrey Bogart").All(); response = client.Send(query) # response.result contains JSON data and response.r contains raw response print response.result # `g` and `V` are synonyms for `graph` and `Vertex` respectively, as they are quite common. query = g.V("Humphrey Bogart").All() response = client.Send(query) # "Humphrey Bogart" is a name, but not an entity. # Let's find the entities with this name in our dataset. # Follow links that are pointing In to our "Humphrey Bogart" node with the predicate "name". query = g.V("Humphrey Bogart").In("").All() response = client.Send(query) # Notice that "name" is a generic predicate in our dataset. # Starting with a movie gives a similar effect. query = g.V("Casablanca").In("name").All() response = client.Send(query) # Relatedly, we can ask the reverse; all ids with the name "Casablanca" query = g.V().Has("name", "Casablanca").All() response = client.Send(query) # Let's get the list of actors in the film query = g.V().Has("name", "Casablanca") \ .Out("/film/film/starring") \ .Out("/film/performance/actor") \ .Out("name") \ .All() response = client.Send(query) # But this is starting to get long. # Let's use a morphism -- a pre-defined path stored in a variable -- as our linkage film_to_actor = g.Morphism().Out("/film/film/starring").Out("/film/performance/actor") query = g.V() \ .Has("name", "Casablanca") \ .Follow(film_to_actor) \ .Out("name") \ .All() response = client.Send(query) # Add data programatically to the JSON result list. Can be any JSON type. query = g.Emit({'name': "John Doe", 'age': 41, 'isActor': True}) response = client.Send(query) Bugs ---- If you encounter a bug, performance issue, or malfunction, please add an `Issues `_ with steps on how to reproduce the problem or feel to free to open a pull request. TODO ---- - Improve Gizmo implementation (Basic steps implemented at the moment) - Add more tests - Add more documentation Open Source Projects in Use ---------------------------- - `requests `_ by @kennethreitz License ------- @ziλasal & @abdullahselek ================================================ FILE: UNLICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 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 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. For more information, please refer to ================================================ FILE: appveyor.yml ================================================ environment: matrix: - PYTHON: "C:\\Python27" - PYTHON: "C:\\Python34" - PYTHON: "C:\\Python35" - PYTHON: "C:\\Python27-x64" DISTUTILS_USE_SDK: "1" - PYTHON: "C:\\Python34-x64" DISTUTILS_USE_SDK: "1" - PYTHON: "C:\\Python35-x64" - PYTHON: "C:\\Python36-x64" DIR_CAYLEY: C:\cayley cache: - '%DIR_CAYLEY% -> appveyor.yml' install: - set PATH=%PYTHON%;%PYTHON%\Scripts;C:\OpenSSL-Win64;%DIR_CAYLEY%;%SystemRoot%\system32;%PATH% - if not exist %DIR_CAYLEY% ( appveyor DownloadFile https://github.com/cayleygraph/cayley/releases/download/v0.7.0/cayley_0.7.0_windows_amd64.zip && 7z x cayley_0.7.0_windows_amd64.zip -y -aoa -oC:\ > NULs ) # - appveyor DownloadFile https://github.com/cayleygraph/cayley/releases/download/v0.7.0/cayley_0.7.0_windows_amd64.zip -Timeout 5000 # - 7z x cayley_0.7.0_windows_amd64.zip cayley_0.7.0_windows_amd64 # - cd cayley_0.7.0_windows_amd64 - ps: $CayleyProcess = Start-Process "cayley http --dbpath=30kmoviedata.nq.gz" -PassThru # We need wheel installed to build wheels - "%PYTHON%\\python.exe -m pip install wheel" - "%PYTHON%\\python.exe -m pip install -r requirements.txt" - "%PYTHON%\\python.exe -m pip install -r requirements.testing.txt" build: off test_script: - "%PYTHON%\\python.exe -m nose --with-coverage --cover-package=pyley" after_test: # This step builds your wheels. - "%PYTHON%\\python.exe setup.py bdist_wheel" artifacts: # bdist_wheel puts your built wheel in the dist directory - path: dist\* on_finish: - ps: Stop-Process -Id $CayleyProcess.Id ================================================ FILE: pyley/__init__.py ================================================ """pyley Python client for an open-source graph database Cayley""" __title__ = 'pyley' __version__ = '0.2.2.2' __author__ = 'Ziya SARIKAYA @ziyasal' __email__ = 'sarikayaziya@gmail.com', __license__ = 'MIT License' __copyright__ = 'Copyright 2014 Ziya SARIKAYA @ziyasal' __url__ = 'https://github.com/ziyasal/pyley' __download_url__ = 'https://pypi.org/pypi/pyley' __description__ = 'Python client for an open-source graph database Cayley' from .pyley import ( CayleyResponse, CayleyClient, GraphObject ) ================================================ FILE: pyley/pyley.py ================================================ import json import requests class CayleyResponse(object): def __init__(self, raw_response, result): self.r = raw_response self.result = result class CayleyClient(object): def __init__(self, url="http://localhost:64210", version="v1"): self.url = "%s/api/%s/query/gizmo" % (url, version) self.write_url = "%s/api/%s/write" % (url, version) self.delete_url = "%s/api/%s/delete" % (url, version) def add_query_limit(self, limit): if isinstance(limit, int): self.url = self.url + '?limit=' + str(limit) else: raise Exception("Invalid parameter for adding query limit, should be integer.") def Send(self, query): if isinstance(query, str): r = requests.post(self.url, data=query.encode('utf-8')) return CayleyResponse(r, r.json()) elif isinstance(query, _GizmoQuery): r = requests.post(self.url, data=str(query).encode('utf-8')) return CayleyResponse(r, r.json()) else: raise Exception("Invalid query parameter in Send") def AddQuad(self, subject, predicate, object_, label=None): return self.AddQuads([(subject, predicate, object_, label)]) def AddQuads(self, quads): quads = [ { "subject": q[0], "predicate": q[1], "object": q[2], "label": None if len(q) < 4 else q[3] } for q in quads ] r = requests.post(self.write_url, json=quads) return CayleyResponse(r, r.json()) def DeleteQuad(self, subject, predicate, object_, label=None): return self.DeleteQuads([(subject, predicate, object_, label)]) def DeleteQuads(self, quads): quads = [ { "subject": q[0], "predicate": q[1], "object": q[2], "label": None if len(q) < 4 else q[3] } for q in quads ] r = requests.post(self.delete_url, json=quads) return CayleyResponse(r, r.json()) class _GizmoQuery(object): queryDeclarations = None def __init__(self): self.queryDeclarations = [] def __str__(self): return ".".join([str(d) for d in self.queryDeclarations]) def _put(self, token, *parameters): q = _QueryDefinition(token, *parameters) self.queryDeclarations.append(q) class GraphObject(object): def V(self): return _Vertex("g.V()") def V(self, *node_ids): builder = [] l = len(node_ids) for index, node_id in enumerate(node_ids): if index == l - 1: builder.append(u"'{0:s}'".format(node_id)) else: builder.append(u"'{0:s}',".format(node_id)) return _Vertex(u"g.V({0:s})".format("".join(builder))) def M(self): return _Morphism("g.Morphism()") def Vertex(self): return self.V() def Vertex(self, *node_ids): if len(node_ids) == 0: return self.V() return self.V(*node_ids) def Morphism(self): return self.M() def Emit(self, data): return "g.Emit({0:s})".format(json.dumps(data, default=lambda o: o.__dict__, sort_keys=True)) class _Path(_GizmoQuery): def __init__(self, parent): _GizmoQuery.__init__(self) self._put(parent) def Out(self, predicate=None, tags=None): self._bounds("Out", predicate, tags) return self def In(self, predicate=None, tags=None): self._bounds("In", predicate, tags) return self def Both(self, predicate=None, tags=None): self._bounds("Both", predicate, tags) return self def _bounds(self, method, predicate=None, tags=None): if predicate is None and tags is None: self._put("%s()", method) elif tags is None: self._put("%s(%s)", method, self._format_input_bounds(predicate)) else: self._put( "%s(%s, %s)", method, self._format_input_bounds(predicate), self._format_input_bounds(tags) ) return self def _format_input_bounds(self, value): if type(value) is dict: return json.dumps(value) if type(value) is str: return "'%s'" % value if value is None: return 'null' return value def Is(self, *nodes): self._put("Is('%s')", "', '".join(nodes)) return self def Has(self, predicate, object): self._put("Has('%s', '%s')", predicate, object) return self def HasR(self, predicate, object): self._put("Has('%s', '%s')", object, predicate) return self def Tag(self, *tags): self._put("Tag(%s)", json.dumps(tags)) return self def Back(self, tag): self._put("Back('%s')", tag) return self def Save(self, predicate, tag): self._put("Save('%s', '%s')", predicate, tag) return self def Intersect(self, query): if not isinstance(query, _Vertex) and type(query) is not str: raise Exception("Invalid parameter in intersect query") self._put("Intersect(%s)", query) return self def Union(self, query): if not isinstance(query, _Vertex) and type(query) is not str: raise Exception("Invalid parameter in union query") self._put("Union(%s)", query) return self def Follow(self, query): if not isinstance(query, _Morphism) and type(query) is not str: raise Exception("Invalid parameter in follow query") self._put("Follow(%s)", query) return self def FollowR(self, query): if not isinstance(query, _Morphism) and type(query) is not str: raise Exception("Invalid parameter in followr query") self._put("FollowR(%s)", query) return self def build(self): return str(self) class _Vertex(_Path): def All(self): self._put("All()") return self def GetLimit(self, limit): self._put("GetLimit(%d)", limit) return self class _Morphism(_Path): pass class _QueryDefinition(object): def __init__(self, token, *parameters): self.token = token self.parameters = parameters def __str__(self): if len(self.parameters) > 0: return str(self.token) % self.parameters else: return str(self.token) ================================================ FILE: requirements.testing.txt ================================================ python-coveralls coverage nose ================================================ FILE: requirements.txt ================================================ requests ================================================ FILE: setup.cfg ================================================ [bdist_wheel] universal = 1 [aliases] test = nosetest [check-manifest] ignore = .travis.yml ================================================ FILE: setup.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, print_function import os import re import codecs from setuptools import setup, find_packages cwd = os.path.abspath(os.path.dirname(__file__)) def read(filename): with codecs.open(os.path.join(cwd, filename), 'rb', 'utf-8') as h: return h.read() metadata = read(os.path.join(cwd, 'pyley', '__init__.py')) def extract_metaitem(meta): meta_match = re.search(r"""^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]""".format(meta=meta), metadata, re.MULTILINE) if meta_match: return meta_match.group(1) raise RuntimeError('Unable to find __{meta}__ string.'.format(meta=meta)) setup( name='pyley', version=extract_metaitem('version'), license=extract_metaitem('license'), description=extract_metaitem('description'), long_description=(read('README.rst')), author=extract_metaitem('author'), author_email=extract_metaitem('email'), url=extract_metaitem('url'), download_url=extract_metaitem('download_url'), packages=find_packages(exclude=('tests')), platforms=['Any'], install_requires=['requests'], tests_require=['python-coveralls', 'coverage', 'nose'], keywords='graph database, cayley, cayley python client, client', include_package_data=True, classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], ) ================================================ FILE: tests/__init__.py ================================================ ================================================ FILE: tests/test_cayley_client.py ================================================ from unittest import TestCase from pyley import CayleyClient, GraphObject _CLIENT_URL = 'http://localhost:64210' class CayleyClientTests(TestCase): def test_add_limit(self): client = CayleyClient(_CLIENT_URL) client.add_query_limit(10000) self.assertEqual(client.url, 'http://localhost:64210/api/v1/query/gizmo?limit=10000') def test_add_limit_fails(self): client = CayleyClient(_CLIENT_URL) with self.assertRaises(Exception): client.add_query_limit('str') def test_send(self): client = CayleyClient(_CLIENT_URL) g = GraphObject() query = g.V().Has("name", "Casablanca") \ .Out("/film/film/starring") \ .Out("/film/performance/actor") \ .Out("name") \ .All() response = client.Send(query) self.assertTrue(response.r.status_code == 200) self.assertTrue(response.r is not None) self.assertTrue(len(response.result) > 0) query = g.V().HasR("name", "Casablanca") \ .Out("/film/film/starring") \ .Out("/film/performance/actor") \ .Out("name") \ .All() response = client.Send(query) self.assertTrue(response.r.status_code == 200) self.assertTrue(response.r is not None) self.assertTrue(len(response.result) > 0) def test_a_add_quad(self): client = CayleyClient(_CLIENT_URL) response = client.AddQuad('foo', 'to', 'bar') self.assertEqual(response.r.status_code, 200) g = GraphObject() query = g.V("foo").Out('to').Is('bar').All() response = client.Send(query) self.assertEqual(len(response.result['result']), 1) def test_b_delete_quad(self): client = CayleyClient(_CLIENT_URL) response = client.DeleteQuad('foo', 'to', 'bar') self.assertEqual(response.r.status_code, 200) g = GraphObject() query = g.V("foo").Out('to').Is("bar").All() response = client.Send(query) self.assertIsNone(response.result['result']) def test_c_add_quads(self): client = CayleyClient(_CLIENT_URL) response = client.AddQuads([ ('foo', 'to', 'bar'), ('baz', 'from', 'quux') ]) self.assertEqual(response.r.status_code, 200) g = GraphObject() query = g.V("foo").Out('to').Is("bar").Union( g.V("baz").Out('from').Is("quux") ).All() response = client.Send(query) self.assertEqual(len(response.result['result']), 2) def test_d_delete_quads(self): client = CayleyClient(_CLIENT_URL) response = client.DeleteQuads([ ('foo', 'to', 'bar'), ('baz', 'from', 'quux') ]) self.assertEqual(response.r.status_code, 200) g = GraphObject() query = g.V("foo").Out('to').Is("bar").Union( g.V("baz").Out('from').Is('quux') ).All() response = client.Send(query) self.assertIsNone(response.result['result']) ================================================ FILE: tests/test_gizmo_query.py ================================================ import unittest from pyley import GraphObject class GizmoQueryTests(unittest.TestCase): def setUp(self): self.opts = dict(url='http://localhost:64210/api/v1/query/gizmo') def test_vertex_query(self): g = GraphObject() query = g.Vertex() self.assertEqual(query.build(), "g.V()") def test_vertex_query_with_parameters(self): g = GraphObject() query = g.V("Humphrey Bogart") actual = query.build() self.assertEqual(actual, "g.V('Humphrey Bogart')") def test_morphism_query(self): g = GraphObject() query = g.Morphism() self.assertEqual(query.build(), "g.Morphism()") def test_out_query(self): g = GraphObject() query = g.V().Out('name') actual = query.build() self.assertEqual(actual, "g.V().Out('name')") def test_out_query_with_predicate(self): g = GraphObject() query = g.V().Out(g.Vertex()) actual = query.build() self.assertEqual(actual, "g.V().Out(g.V())") def test_out_query_with_predicate_as_dict_and_label(self): g = GraphObject() query = g.V().Out(['foo', 'bar'], 'qux') actual = query.build() self.assertEqual(actual, "g.V().Out(['foo', 'bar'], 'qux')") def test_out_query_with_predicate_as_none_and_label_as_dict(self): g = GraphObject() query = g.V().Out(None, ['foo', 'bar']) actual = query.build() self.assertEqual(actual, "g.V().Out(null, ['foo', 'bar'])") def test_in_query(self): g = GraphObject() query = g.V().In("name").All() actual = query.build() self.assertEqual(actual, "g.V().In('name').All()") def test_both(self): g = GraphObject() query = g.V("F").Both("follows") actual = query.build() print(actual) self.assertEqual(actual, "g.V('F').Both('follows')") def test_is(self): g = GraphObject() query = g.V().Is('B', 'C') actual = query.build() self.assertEqual(actual, "g.V().Is('B', 'C')") def test_tag(self): g = GraphObject() query = g.V().Tag('B', 'C') actual = query.build() self.assertEqual(actual, 'g.V().Tag(["B", "C"])') def test_save(self): g = GraphObject() query = g.V().Save('B', 'C') actual = query.build() self.assertEqual(actual, "g.V().Save('B', 'C')") def test_back(self): g = GraphObject() query = g.V().Back('B') actual = query.build() self.assertEqual(actual, "g.V().Back('B')") def test_all_query(self): g = GraphObject() query = g.V("Humphrey Bogart").All() actual = query.build() self.assertEqual(actual, "g.V('Humphrey Bogart').All()") def test_has_query(self): g = GraphObject() query = g.V().Has("name", "Casablanca").All() actual = query.build() self.assertEqual(actual, "g.V().Has('name', 'Casablanca').All()") def test_complex_query1(self): g = GraphObject() query = g.V().Has("name", "Casablanca") \ .Out("/film/film/starring") \ .Out("/film/performance/actor") \ .Out("name") \ .All() actual = query.build() self.assertEqual(actual, "g.V().Has('name', 'Casablanca')" ".Out('/film/film/starring')" ".Out('/film/performance/actor')" ".Out('name')" ".All()") def test_follow_with_morphism_path_and_typed_query(self): g = GraphObject() film_to_actor = g.Morphism().Out("/film/film/starring").Out("/film/performance/actor") query = g.V().Has("name", "Casablanca").Follow(film_to_actor).Out("name").All() actual = query.build() self.assertEqual(actual, "g.V().Has('name', 'Casablanca')" ".Follow(" "g.Morphism().Out('/film/film/starring').Out('/film/performance/actor')" ").Out('name')" ".All()") def test_follow_with_morphism_path_and_str_query(self): g = GraphObject() film_to_actor = g.Morphism().Out("/film/film/starring").Out("/film/performance/actor") query = g.V().Has("name", "Casablanca").Follow(film_to_actor.build()).Out("name").All() actual = query.build() self.assertEqual(actual, "g.V().Has('name', 'Casablanca')" ".Follow(" "g.Morphism().Out('/film/film/starring').Out('/film/performance/actor')" ").Out('name')" ".All()") def test_follow_with_vertex(self): g = GraphObject() with self.assertRaises(Exception): g.V().Follow(g.V()).build() def test_union(self): g = GraphObject() query = g.Vertex().Union(g.Vertex()) actual = query.build() self.assertEqual(actual, "g.V().Union(g.V())") def test_intersect(self): g = GraphObject() query = g.Vertex().Intersect(g.Vertex()) actual = query.build() self.assertEqual(actual, "g.V().Intersect(g.V())") def test_get_limit(self): g = GraphObject() query = g.Vertex().GetLimit(5) actual = query.build() self.assertEqual(actual, "g.V().GetLimit(5)") def test_emit(self): g = GraphObject() query = g.Emit({'name': 'John', 'lastName': 'DOE', 'age': 25}) self.assertEqual(query, 'g.Emit({"age": 25, "lastName": "DOE", "name": "John"})') if __name__ == '__main__': unittest.main() ================================================ FILE: tox.ini ================================================ [tox] envlist = clean,py27,py3,py36,pypy,pypy3 skip_missing_interpreters = True [testenv] deps = -Ur{toxinidir}/requirements.testing.txt commands = python -m nose whitelist_externals = pyenv install -s 2.7.11 pyenv install -s 3.6.1 pyenv install -s pypy-5.3.1 pyenv local 2.7.11 3.6.1 pypy-5.3.1 [testenv:clean] deps = coverage commands = coverage erase [testenv:report] commands = nosetests --with-coverage --cover-package=pyley