summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Car <nicholas.car@surroundaustralia.com>2020-05-05 22:02:22 +1000
committerNicholas Car <nicholas.car@surroundaustralia.com>2020-05-05 22:02:22 +1000
commit802678ce0aba5ac15c27be4dab7519e79c34d2b9 (patch)
treef08b5cdede584912fc23f4e65d10bff38241b1bf
parent88338195dc18de91c7122c025cc619951c876043 (diff)
parentf6fde7ed2bbd75793083631c04ae69bdc2496383 (diff)
downloadrdflib-namespaces_all.tar.gz
Merge branch 'master' into namespaces_allnamespaces_all
-rw-r--r--.travis.yml9
-rw-r--r--README.md4
-rw-r--r--docs/_static/RDFlib-500.pngbin0 -> 26062 bytes
-rw-r--r--docs/_static/RDFlib.ico (renamed from docs/_static/logo-rdflib.ico)bin3262 -> 3262 bytes
-rw-r--r--docs/_static/RDFlib.pngbin0 -> 21014 bytes
-rw-r--r--docs/_static/RDFlib.svg47
-rw-r--r--docs/conf.py2
-rw-r--r--docs/sphinx-requirements.txt2
-rw-r--r--rdflib/extras/external_graph_libs.py2
-rw-r--r--rdflib/graph.py68
-rw-r--r--rdflib/plugins/stores/sparqlconnector.py6
-rw-r--r--rdflib/plugins/stores/sparqlstore.py8
-rw-r--r--setup.py7
-rw-r--r--test/test_batch_add.py89
-rw-r--r--test/test_sparqlstore.py78
-rw-r--r--tox.ini4
16 files changed, 302 insertions, 24 deletions
diff --git a/.travis.yml b/.travis.yml
index f37f0750..2d2ada1d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,4 @@
# http://travis-ci.org/#!/RDFLib/rdflib
-sudo: false
language: python
branches:
only:
@@ -10,16 +9,14 @@ git:
depth: 3
python:
- - 2.7
- - 3.4
- 3.5
- 3.6
+ - 3.7
-matrix:
+jobs:
include:
- - python: 3.7
+ - python: 3.8
dist: xenial
- sudo: true
before_install:
- pip install -U setuptools pip # seems travis comes with a too old setuptools for html5lib
diff --git a/README.md b/README.md
index 3160930d..0c123419 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-![](docs/_static/logo-rdflib.png)
+![](docs/_static/RDFlib.png)
RDFLib
======
@@ -8,7 +8,7 @@ RDFLib
[![PyPI](https://img.shields.io/pypi/v/rdflib.svg)](https://pypi.python.org/pypi/rdflib)
[![PyPI](https://img.shields.io/pypi/pyversions/rdflib.svg)](https://pypi.python.org/pypi/rdflib)
-RDFLib is a pure Python package work working with [RDF](http://www.w3.org/RDF/). RDFLib contains most things you need to work with RDF, including:
+RDFLib is a pure Python package for working with [RDF](http://www.w3.org/RDF/). RDFLib contains most things you need to work with RDF, including:
* parsers and serializers for RDF/XML, N3, NTriples, N-Quads, Turtle, TriX, Trig and JSON-LD (via a plugin).
* a Graph interface which can be backed by any one of a number of Store implementations
diff --git a/docs/_static/RDFlib-500.png b/docs/_static/RDFlib-500.png
new file mode 100644
index 00000000..8312a071
--- /dev/null
+++ b/docs/_static/RDFlib-500.png
Binary files differ
diff --git a/docs/_static/logo-rdflib.ico b/docs/_static/RDFlib.ico
index b667f6b6..b667f6b6 100644
--- a/docs/_static/logo-rdflib.ico
+++ b/docs/_static/RDFlib.ico
Binary files differ
diff --git a/docs/_static/RDFlib.png b/docs/_static/RDFlib.png
new file mode 100644
index 00000000..435f07e4
--- /dev/null
+++ b/docs/_static/RDFlib.png
Binary files differ
diff --git a/docs/_static/RDFlib.svg b/docs/_static/RDFlib.svg
new file mode 100644
index 00000000..8e8d3fc1
--- /dev/null
+++ b/docs/_static/RDFlib.svg
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#329545;}
+ .st1{fill:#FDD43B;}
+ .st2{fill:#144A99;}
+ .st3{fill:#FFFFFF;}
+</style>
+<g id="RDFlib">
+ <path id="path1948" class="st0" d="M247.4,33.3c-17.7,0.1-34.5,1.6-49.4,4.2c-43.7,7.7-51.7,23.9-51.7,53.7v39.4h103.4v13.1H146.4
+ h-38.8c-30,0-56.4,18.1-64.6,52.4c-9.5,39.4-9.9,63.9,0,105.1c7.3,30.6,24.9,52.4,54.9,52.4h35.5v-47.2c0-34.1,29.5-64.2,64.6-64.2
+ h103.3c28.7,0,51.7-23.7,51.7-52.5V91.3c0-28-23.6-49.1-51.7-53.7C283.5,34.6,265.1,33.3,247.4,33.3z M191.5,65
+ c10.7,0,19.4,8.9,19.4,19.8c0,10.9-8.7,19.6-19.4,19.6c-10.7,0-19.4-8.8-19.4-19.6C172.1,73.9,180.8,65,191.5,65z"/>
+ <path id="path1950" class="st1" d="M365.9,143.8v45.9c0,35.6-30.2,65.5-64.6,65.5H198c-28.3,0-51.7,24.2-51.7,52.5v98.4
+ c0,28,24.4,44.5,51.7,52.5c32.7,9.6,64.1,11.4,103.3,0c26-7.5,51.7-22.7,51.7-52.5v-39.4H249.7v-13.1H353h51.7
+ c30,0,41.2-21,51.7-52.4c10.8-32.4,10.3-63.5,0-105.1c-7.4-29.9-21.6-52.4-51.7-52.4H365.9z M307.8,393.1
+ c10.7,0,19.4,8.8,19.4,19.6c0,10.9-8.7,19.8-19.4,19.8c-10.7,0-19.4-8.9-19.4-19.8C288.4,401.9,297.1,393.1,307.8,393.1z"/>
+ <path id="XMLID_41_" class="st2" d="M342.1,345.7c5.2-17,2.3-36.4-11.5-51c-3.9-4.2-8.6-7.7-13.6-10.4c-1.8-1-3.6-1.8-5.5-2.5
+ c0,0-10.5-5.3-11.5-43.2c-0.3-12.7-0.5-28.2,5.9-39.7c0.9-1.6,1.9-3.2,3.3-4.4c7.3-6.2,15.4-10.5,20.7-18.8
+ c5.4-8.5,8.4-18.4,8.4-28.4c0-43.9-53.7-68.6-87.2-40.3c-5.1,4.3-9.4,9.6-12.5,15.6c-5.6,10.6-7.2,22.2-5.5,33.2
+ c0,0,2.5,12.8-29.8,32.9c-28.6,17.8-42.5,13-45.3,11.7c-8.2-5.2-17.8-8.2-28.2-8.2c-44,0-68.6,53.8-40.2,87.3
+ c4.3,5.1,9.6,9.3,15.5,12.4c19.2,10.2,41.9,7.4,57.9-5.4c0,0,11.4-9,45.2,9.2c26.7,14.3,30.7,28.4,31.2,33.7
+ c1.4,14.3,3.7,26.6,13.8,37.7C280.8,397,330.5,384,342.1,345.7z M215.1,280.6c-27.8-14.9-32-27.4-32.6-31.2
+ c0.4-4.5,0.1-9.1-0.6-13.5l0.2,0.3c0,0-2.3-12.1,29.6-31.9c28.5-17.7,41.5-14.2,43.9-13.3c1.6,1.1,3.2,2,4.8,2.9
+ c3.2,1.7,6.5,3,9.9,4c3.9,3.7,11.1,14.3,11.9,42.2c0.8,28.1-7.5,38.9-12,42.7c-4.6,2.1-9,4.9-12.9,8.3
+ C253,292.7,240.8,294.4,215.1,280.6z"/>
+ <g id="XMLID_32_">
+ <path id="XMLID_33_" class="st3" d="M253.8,117.4c-15.4,16.8-15.7,41.6-0.9,55.6c-7.3-7.1-7.2-21.7,0.2-35.8
+ c1-1.3,3.7-4.2,7.7-2.9c0.4,0.1,0.7,0.2,0.8,0.2c0.9,0.2,1.8,0.3,2.8,0.3c6.1-0.3,10.9-5.5,10.6-11.6c-0.1-2.7-1.3-5.2-3-7
+ c14.2-9.3,30.5-10.4,37.2-4.3l0.3,0C294.3,97.9,269.3,100.4,253.8,117.4z"/>
+ </g>
+ <g id="XMLID_29_">
+ <path id="XMLID_31_" class="st3" d="M98.6,272.9c-0.1-0.1-0.3-0.3-0.4-0.4c0.1,0.1,0.2,0.2,0.3,0.3L98.6,272.9z"/>
+ <path id="XMLID_30_" class="st3" d="M99.1,216.9c-15.4,16.8-15.7,41.6-0.9,55.6c-7.3-7.1-7.2-21.7,0.2-35.8c1-1.3,3.7-4.2,7.7-2.9
+ c0.4,0.1,0.7,0.2,0.8,0.2c0.9,0.2,1.8,0.3,2.8,0.3c6.1-0.3,10.9-5.5,10.6-11.6c-0.1-2.7-1.3-5.2-3-7c14.2-9.3,30.5-10.4,37.2-4.3
+ l0.3,0C139.6,197.4,114.7,199.9,99.1,216.9z"/>
+ </g>
+ <g id="XMLID_26_">
+ <path id="XMLID_28_" class="st3" d="M262.4,357.2c-0.1-0.1-0.3-0.3-0.4-0.4c0.1,0.1,0.2,0.2,0.3,0.3L262.4,357.2z"/>
+ <path id="XMLID_27_" class="st3" d="M262.9,301.2c-15.4,16.8-15.7,41.6-0.9,55.6c-7.3-7.1-7.2-21.7,0.2-35.8
+ c1-1.3,3.7-4.2,7.7-2.9c0.4,0.1,0.7,0.2,0.8,0.2c0.9,0.2,1.8,0.3,2.8,0.3c6.1-0.3,10.9-5.5,10.6-11.6c-0.1-2.7-1.3-5.2-3-7
+ c14.2-9.3,30.5-10.4,37.2-4.3l0.3,0C303.4,281.7,278.4,284.2,262.9,301.2z"/>
+ </g>
+</g>
+</svg>
diff --git a/docs/conf.py b/docs/conf.py
index 76f893fb..dbecc10b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -149,7 +149,7 @@ html_theme_path = [
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
-html_logo = "_static/logo-rdflib.png"
+html_logo = "_static/RDFlib.png"
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
diff --git a/docs/sphinx-requirements.txt b/docs/sphinx-requirements.txt
index 55809050..45583540 100644
--- a/docs/sphinx-requirements.txt
+++ b/docs/sphinx-requirements.txt
@@ -1,3 +1,3 @@
-sphinx==3.0.2
+sphinx==3.0.3
sphinxcontrib-apidoc
git+https://github.com/gniezen/n3pygments.git
diff --git a/rdflib/extras/external_graph_libs.py b/rdflib/extras/external_graph_libs.py
index 8617b370..873805b4 100644
--- a/rdflib/extras/external_graph_libs.py
+++ b/rdflib/extras/external_graph_libs.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
# encoding: utf-8
from __future__ import absolute_import
from __future__ import division
diff --git a/rdflib/graph.py b/rdflib/graph.py
index 80dc02f8..a92f3bc5 100644
--- a/rdflib/graph.py
+++ b/rdflib/graph.py
@@ -271,6 +271,7 @@ __all__ = [
"Dataset",
"UnSupportedAggregateOperation",
"ReadOnlyGraphAggregate",
+ "BatchAddGraph",
]
@@ -2013,6 +2014,73 @@ def _assertnode(*terms):
return True
+class BatchAddGraph(object):
+ '''
+ Wrapper around graph that turns calls to :meth:`add` (and optionally, :meth:`addN`)
+ into calls to :meth:`~rdflib.graph.Graph.addN`.
+
+ :Parameters:
+
+ - `graph`: The graph to wrap
+ - `batch_size`: The maximum number of triples to buffer before passing to
+ `graph`'s `addN`
+ - `batch_addn`: If True, then even calls to `addN` will be batched according to
+ `batch_size`
+
+ :ivar graph: The wrapped graph
+ :ivar count: The number of triples buffered since initaialization or the last call
+ to :meth:`reset`
+ :ivar batch: The current buffer of triples
+
+ '''
+
+ def __init__(self, graph, batch_size=1000, batch_addn=False):
+ if not batch_size or batch_size < 2:
+ raise ValueError("batch_size must be a positive number")
+ self.graph = graph
+ self.__graph_tuple = (graph,)
+ self.__batch_size = batch_size
+ self.__batch_addn = batch_addn
+ self.reset()
+
+ def reset(self):
+ '''
+ Manually clear the buffered triples and reset the count to zero
+ '''
+ self.batch = []
+ self.count = 0
+
+ def add(self, triple_or_quad):
+ '''
+ Add a triple to the buffer
+
+ :param triple: The triple to add
+ '''
+ if len(self.batch) >= self.__batch_size:
+ self.graph.addN(self.batch)
+ self.batch = []
+ self.count += 1
+ if len(triple_or_quad) == 3:
+ self.batch.append(triple_or_quad + self.__graph_tuple)
+ else:
+ self.batch.append(triple_or_quad)
+
+ def addN(self, quads):
+ if self.__batch_addn:
+ for q in quads:
+ self.add(q)
+ else:
+ self.graph.addN(quads)
+
+ def __enter__(self):
+ self.reset()
+ return self
+
+ def __exit__(self, *exc):
+ if exc[0] is None:
+ self.graph.addN(self.batch)
+
+
def test():
import doctest
diff --git a/rdflib/plugins/stores/sparqlconnector.py b/rdflib/plugins/stores/sparqlconnector.py
index ee981419..abb69a55 100644
--- a/rdflib/plugins/stores/sparqlconnector.py
+++ b/rdflib/plugins/stores/sparqlconnector.py
@@ -87,6 +87,7 @@ class SPARQLConnector(object):
if self.method == 'GET':
args['params'].update(params)
elif self.method == 'POST':
+ args['headers'].update({'Content-Type': 'application/sparql-query'})
args['data'] = params
else:
raise SPARQLConnectorException("Unknown method %s" % self.method)
@@ -106,7 +107,10 @@ class SPARQLConnector(object):
if default_graph:
params["using-graph-uri"] = default_graph
- headers = {'Accept': _response_mime_types[self.returnFormat]}
+ headers = {
+ 'Accept': _response_mime_types[self.returnFormat],
+ 'Content-Type': 'application/sparql-update',
+ }
args = dict(self.kwargs)
diff --git a/rdflib/plugins/stores/sparqlstore.py b/rdflib/plugins/stores/sparqlstore.py
index 5f7446ce..4c93e213 100644
--- a/rdflib/plugins/stores/sparqlstore.py
+++ b/rdflib/plugins/stores/sparqlstore.py
@@ -493,8 +493,8 @@ class SPARQLUpdateStore(SPARQLStore):
def open(self, configuration, create=False):
"""
sets the endpoint URLs for this SPARQLStore
- :param configuration: either a tuple of (queryEndpoint, update_endpoint),
- or a string with the query endpoint
+ :param configuration: either a tuple of (query_endpoint, update_endpoint),
+ or a string with the endpoint which is configured as query and update endpoint
:param create: if True an exception is thrown.
"""
@@ -507,9 +507,7 @@ class SPARQLUpdateStore(SPARQLStore):
self.update_endpoint = configuration[1]
else:
self.query_endpoint = configuration
-
- if not self.update_endpoint:
- self.update_endpoint = self.endpoint
+ self.update_endpoint = configuration
def _transaction(self):
if self._edits is None:
diff --git a/setup.py b/setup.py
index c115f068..46a8e15e 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ from setuptools import setup, find_packages
kwargs = {}
kwargs['install_requires'] = [ 'six', 'isodate', 'pyparsing']
-kwargs['tests_require'] = ['html5lib', 'networkx', 'nose', 'doctest-ignore-unicode']
+kwargs['tests_require'] = ['html5lib', 'networkx', 'nose', 'doctest-ignore-unicode', 'requests']
kwargs['test_suite'] = "nose.collector"
kwargs['extras_require'] = {
'html': ['html5lib'],
@@ -43,15 +43,14 @@ setup(
url="https://github.com/RDFLib/rdflib",
license="BSD-3-Clause",
platforms=["any"],
+ python_requires='>=3.5',
classifiers=[
"Programming Language :: Python",
- "Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 2.7",
- "Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"License :: OSI Approved :: BSD License",
"Topic :: Software Development :: Libraries :: Python Modules",
"Operating System :: OS Independent",
diff --git a/test/test_batch_add.py b/test/test_batch_add.py
new file mode 100644
index 00000000..1747100c
--- /dev/null
+++ b/test/test_batch_add.py
@@ -0,0 +1,89 @@
+import unittest
+from rdflib.graph import Graph, BatchAddGraph
+from rdflib.term import URIRef
+
+
+class TestBatchAddGraph(unittest.TestCase):
+ def test_batch_size_zero_denied(self):
+ with self.assertRaises(ValueError):
+ BatchAddGraph(Graph(), batch_size=0)
+
+ def test_batch_size_none_denied(self):
+ with self.assertRaises(ValueError):
+ BatchAddGraph(Graph(), batch_size=None)
+
+ def test_batch_size_one_denied(self):
+ with self.assertRaises(ValueError):
+ BatchAddGraph(Graph(), batch_size=1)
+
+ def test_batch_size_negative_denied(self):
+ with self.assertRaises(ValueError):
+ BatchAddGraph(Graph(), batch_size=-12)
+
+ def test_exit_submits_partial_batch(self):
+ trip = (URIRef('a'), URIRef('b'), URIRef('c'))
+ g = Graph()
+ with BatchAddGraph(g, batch_size=10) as cut:
+ cut.add(trip)
+ self.assertIn(trip, g)
+
+ def test_add_more_than_batch_size(self):
+ trips = [(URIRef('a'), URIRef('b%d' % i), URIRef('c%d' % i))
+ for i in range(12)]
+ g = Graph()
+ with BatchAddGraph(g, batch_size=10) as cut:
+ for trip in trips:
+ cut.add(trip)
+ self.assertEqual(12, len(g))
+
+ def test_add_quad_for_non_conjunctive_empty(self):
+ '''
+ Graph drops quads that don't match our graph. Make sure we do the same
+ '''
+ g = Graph(identifier='http://example.org/g')
+ badg = Graph(identifier='http://example.org/badness')
+ with BatchAddGraph(g) as cut:
+ cut.add((URIRef('a'), URIRef('b'), URIRef('c'), badg))
+ self.assertEqual(0, len(g))
+
+ def test_add_quad_for_non_conjunctive_pass_on_context_matches(self):
+ g = Graph()
+ with BatchAddGraph(g) as cut:
+ cut.add((URIRef('a'), URIRef('b'), URIRef('c'), g))
+ self.assertEqual(1, len(g))
+
+ def test_no_addN_on_exception(self):
+ '''
+ Even if we've added triples so far, it may be that attempting to add the last
+ batch is the cause of our exception, so we don't want to attempt again
+ '''
+ g = Graph()
+ trips = [(URIRef('a'), URIRef('b%d' % i), URIRef('c%d' % i))
+ for i in range(12)]
+
+ try:
+ with BatchAddGraph(g, batch_size=10) as cut:
+ for i, trip in enumerate(trips):
+ cut.add(trip)
+ if i == 11:
+ raise Exception('myexc')
+ except Exception as e:
+ if str(e) != 'myexc':
+ pass
+ self.assertEqual(10, len(g))
+
+ def test_addN_batching_addN(self):
+ class MockGraph(object):
+ def __init__(self):
+ self.counts = []
+
+ def addN(self, quads):
+ self.counts.append(sum(1 for _ in quads))
+
+ g = MockGraph()
+ quads = [(URIRef('a'), URIRef('b%d' % i), URIRef('c%d' % i), g)
+ for i in range(12)]
+
+ with BatchAddGraph(g, batch_size=10, batch_addn=True) as cut:
+ cut.addN(quads)
+ self.assertEqual(g.counts, [10, 2])
diff --git a/test/test_sparqlstore.py b/test/test_sparqlstore.py
index 26a69460..a0a93b57 100644
--- a/test/test_sparqlstore.py
+++ b/test/test_sparqlstore.py
@@ -4,7 +4,10 @@ import os
import unittest
from nose import SkipTest
from requests import HTTPError
-
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import socket
+from threading import Thread
+import requests
try:
assert len(urlopen("http://dbpedia.org/sparql").read()) > 0
@@ -67,5 +70,78 @@ class SPARQLStoreDBPediaTestCase(unittest.TestCase):
assert type(i[0]) == Literal, i[0].n3()
+class SPARQLStoreUpdateTestCase(unittest.TestCase):
+ def setUp(self):
+ port = self.setup_mocked_endpoint()
+ self.graph = Graph(store="SPARQLUpdateStore", identifier=URIRef("urn:ex"))
+ self.graph.open(("http://localhost:{port}/query".format(port=port),
+ "http://localhost:{port}/update".format(port=port)), create=False)
+ ns = list(self.graph.namespaces())
+ assert len(ns) > 0, ns
+
+ def setup_mocked_endpoint(self):
+ # Configure mock server.
+ s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
+ s.bind(('localhost', 0))
+ address, port = s.getsockname()
+ s.close()
+ mock_server = HTTPServer(('localhost', port), SPARQL11ProtocolStoreMock)
+
+ # Start running mock server in a separate thread.
+ # Daemon threads automatically shut down when the main process exits.
+ mock_server_thread = Thread(target=mock_server.serve_forever)
+ mock_server_thread.setDaemon(True)
+ mock_server_thread.start()
+ print("Started mocked sparql endpoint on http://localhost:{port}/".format(port=port))
+ return port
+
+ def tearDown(self):
+ self.graph.close()
+
+ def test_Query(self):
+ query = "insert data {<urn:s> <urn:p> <urn:o>}"
+ res = self.graph.update(query)
+ print(res)
+
+
+class SPARQL11ProtocolStoreMock(BaseHTTPRequestHandler):
+ def do_POST(self):
+ """
+ If the body should be analysed as well, just use:
+ ```
+ body = self.rfile.read(int(self.headers['Content-Length'])).decode()
+ print(body)
+ ```
+ """
+ contenttype = self.headers.get("Content-Type")
+ if self.path == "/query":
+ if self.headers.get("Content-Type") == "application/sparql-query":
+ pass
+ elif self.headers.get("Content-Type") == "application/x-www-form-urlencoded":
+ pass
+ else:
+ self.send_response(requests.codes.not_acceptable)
+ self.end_headers()
+ elif self.path == "/update":
+ if self.headers.get("Content-Type") == "application/sparql-update":
+ pass
+ elif self.headers.get("Content-Type") == "application/x-www-form-urlencoded":
+ pass
+ else:
+ self.send_response(requests.codes.not_acceptable)
+ self.end_headers()
+ else:
+ self.send_response(requests.codes.not_found)
+ self.end_headers()
+ self.send_response(requests.codes.ok)
+ self.end_headers()
+ return
+
+ def do_GET(self):
+ # Process an HTTP GET request and return a response with an HTTP 200 status.
+ self.send_response(requests.codes.ok)
+ self.end_headers()
+ return
+
if __name__ == '__main__':
unittest.main()
diff --git a/tox.ini b/tox.ini
index ee712287..b5b588fb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
[tox]
envlist =
- py27,py34,py35,py36
+ py35,py36,py37,py38
[testenv]
setenv =
@@ -20,7 +20,7 @@ deps =
[testenv:cover]
basepython =
- python2.7
+ python3.7
commands =
{envpython} run_tests.py --where=./ \
--with-coverage --cover-html --cover-html-dir=./coverage \