Skip to content

Commit

Permalink
Added CRUD endpoint
Browse files Browse the repository at this point in the history
Fixes #24
  • Loading branch information
cecton committed Aug 9, 2017
1 parent 23465c2 commit 60196e9
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 6 deletions.
20 changes: 16 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
sudo: false
sudo: true

language: python

services:
- docker

python:
- "3.5"
- "3.6"

install:
- pip install tox tox-travis codecov

before_script:
- docker run -d --name travis-virtuoso -p 8890:8890 -e SPARQL_UPDATE=true tenforce/virtuoso:1.2.0-virtuoso7.2.2

script:
- tox
after_success:
- codecov
- tox

after_script:
- docker rm -vf travis-virtuoso

after_success:
- codecov

jobs:
include:
Expand Down
50 changes: 49 additions & 1 deletion aiosparql/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import asyncio
import logging
import re
from io import IOBase
from math import ceil, log10
from string import Formatter
from textwrap import dedent, indent
from typing import Dict, Optional
from typing import Dict, Optional, Union

from .syntax import IRI, MetaNamespace

Expand Down Expand Up @@ -81,12 +82,14 @@ def format_field(self, value, format_spec):
class SPARQLClient:
def __init__(self, endpoint: str, *,
update_endpoint: Optional[str] = None,
crud_endpoint: Optional[str] = None,
prefixes: Optional[Dict[str, IRI]] = None,
graph: Optional[IRI] = None,
**kwargs):
self._closed = False
self._endpoint = endpoint
self._update_endpoint = update_endpoint
self._crud_endpoint = crud_endpoint
self._graph = graph
self.session = aiohttp.ClientSession(**kwargs)
self._generate_prefixes(prefixes)
Expand All @@ -99,6 +102,10 @@ def endpoint(self):
def update_endpoint(self):
return self._update_endpoint or self._endpoint

@property
def crud_endpoint(self):
return self._crud_endpoint

@property
def graph(self):
return self._graph
Expand Down Expand Up @@ -178,6 +185,47 @@ async def update(self, query: str, *args, **keywords) -> dict:
explanation=explanation)
return await resp.json()

def _crud_request(self, method, graph=None, data=None, accept=None,
content_type=None):
if not self.crud_endpoint:
raise ValueError("CRUD endpoint not specified")
url = self.crud_endpoint
if graph:
params = {'graph': graph.value}
elif self.graph:
params = {'graph': self.graph.value}
else:
params = None
url += "?default"
headers = {}
if content_type:
headers["Content-Type"] = content_type
if accept:
headers["Accept"] = accept
logger.debug("Sending %s request to CRUD endpoint %s with headers "
"%r, and params %r", method, url, headers, params)
return self.session.request(method, url, params=params,
headers=headers, data=data)

def get(self, *, format: str, graph: Optional[IRI] = None):
return self._crud_request("GET", graph=graph, accept=format)

async def put(self, data: Union[bytes, IOBase], *, format: str,
graph: Optional[IRI] = None):
async with self._crud_request("PUT", graph=graph, data=data,
content_type=format) as resp:
resp.raise_for_status()

async def delete(self, graph: Optional[IRI] = None):
async with self._crud_request("DELETE", graph=graph) as resp:
resp.raise_for_status()

async def post(self, data: Union[bytes, IOBase], *, format: str,
graph: Optional[IRI] = None):
async with self._crud_request("POST", graph=graph, data=data,
content_type=format) as resp:
resp.raise_for_status()

@property
def closed(self):
return self._closed
Expand Down
16 changes: 15 additions & 1 deletion aiosparql/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ async def start_server(self):
if kwargs.get('update_endpoint'):
kwargs['update_endpoint'] = \
self.make_url(kwargs['update_endpoint'])
if kwargs.get('crud_endpoint'):
kwargs['crud_endpoint'] = self.make_url(kwargs['crud_endpoint'])
self._session = SPARQLClient(loop=self._loop,
**kwargs)

Expand All @@ -49,14 +51,26 @@ def session(self):
return self._session

def make_url(self, path):
return self._server.make_url(path)
return str(self._server.make_url(path))

def query(self, query, *args, **keywords):
return self.session.query(query, *args, **keywords)

def update(self, query, *args, **keywords):
return self.session.update(query, *args, **keywords)

def get(self, *args, **kwargs):
return self.session.get(*args, **kwargs)

def put(self, *args, **kwargs):
return self.session.put(*args, **kwargs)

def delete(self, *args, **kwargs):
return self.session.delete(*args, **kwargs)

def post(self, *args, **kwargs):
return self.session.post(*args, **kwargs)

async def close(self):
if not self._closed:
await self._session.close()
Expand Down
Empty file added tests/integration/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions tests/integration/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import aiohttp
import unittest
import uuid
from aiohttp.test_utils import setup_test_loop, unittest_run_loop
from os import environ as ENV

from aiosparql.client import SPARQLClient
from aiosparql.syntax import IRI

__all__ = ['IntegrationTestCase', 'unittest_run_loop']


class IntegrationTestCase(unittest.TestCase):
async def _create_client(self):
return SPARQLClient(
ENV.get("SPARQL_ENDPOINT", "http://localhost:8890/sparql"),
update_endpoint=ENV.get("SPARQL_UPDATE_ENDPOINT"),
crud_endpoint=ENV.get("SPARQL_UPDATE_ENDPOINT",
"http://localhost:8890/sparql-graph-crud"),
graph=self.graph)

def _generate_random_graph(self):
return IRI("http://aiosparql.org/%s" % uuid.uuid4().hex[:7])

def setUp(self):
self.loop = setup_test_loop()
self.graph = self._generate_random_graph()
self.client = self.loop.run_until_complete(self._create_client())

def tearDown(self):
try:
self.loop.run_until_complete(self.client.delete())
except aiohttp.ClientResponseError as exc:
if exc.code != 404:
raise
self.loop.run_until_complete(self.client.close())
self.loop.close()
28 changes: 28 additions & 0 deletions tests/integration/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from tests.integration.helpers import IntegrationTestCase, unittest_run_loop


sample_data = """\
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix ex: <http://example.org/stuff/1.0/> .
<http://www.w3.org/TR/rdf-syntax-grammar>
dc:title "RDF/XML Syntax Specification (Revised)" ;
ex:editor [
ex:fullname "Dave Beckett";
ex:homePage <http://purl.org/net/dajobe/>
] .
"""
sample_format = "text/turtle"


class Client(IntegrationTestCase):
@unittest_run_loop
async def test_crud(self):
await self.client.put(sample_data, format=sample_format)
await self.client.delete()
await self.client.post(sample_data, format=sample_format)
async with self.client.get(format="text/turtle") as res:
self.assertEqual(res.status, 200)
text = await res.text()
self.assertIn("@prefix", text)
Empty file added tests/unit/__init__.py
Empty file.
92 changes: 92 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import aiohttp
import json
import unittest
from aiohttp import web
Expand All @@ -22,17 +23,56 @@ async def sparql_endpoint(request):
content_type="application/json")


async def crud_endpoint(request):
request.app['last_request'] = request
if request.method == "PATCH":
return web.Response(text="{}", content_type="application/json")
else:
raise web.HTTPNoContent()


class ClientWithoutGraph(AioSPARQLTestCase):
client_kwargs = {
"endpoint": "/sparql",
"crud_endpoint": "/crud",
}

async def get_application(self):
app = web.Application()
app.router.add_route('*', '/crud', crud_endpoint)
return app

@unittest_run_loop
async def test_get(self):
async with self.client.get(format="some/format") as response:
self.assertIsInstance(response, aiohttp.ClientResponse)
self.assertEqual(self.app['last_request'].method, "GET")
self.assertEqual(self.app['last_request'].query_string, "default")
self.assertEqual(self.app['last_request'].headers['Accept'],
"some/format")

async with self.client.get(format="some/format", graph=IRI("foo")) \
as response:
self.assertIsInstance(response, aiohttp.ClientResponse)
self.assertEqual(self.app['last_request'].method, "GET")
self.assertEqual(self.app['last_request'].query_string, "graph=foo")
self.assertEqual(self.app['last_request'].headers['Accept'],
"some/format")


class Client(AioSPARQLTestCase):
client_kwargs = {
"endpoint": "/sparql",
"update_endpoint": "/sparql-update",
"crud_endpoint": "/crud",
"graph": IRI("http://mu.semte.ch/test-application"),
}

async def get_application(self):
app = web.Application()
app.router.add_post('/sparql', sparql_endpoint)
app.router.add_post('/sparql-update', sparql_endpoint)
app.router.add_route('*', '/crud', crud_endpoint)
return app

@unittest_run_loop
Expand Down Expand Up @@ -83,6 +123,58 @@ async def test_update(self):
with self.assertRaises(SPARQLRequestFailed):
await self.client.update("failure")

@unittest_run_loop
async def test_get(self):
async with self.client.get(format="some/format") as response:
self.assertIsInstance(response, aiohttp.ClientResponse)
self.assertEqual(self.app['last_request'].method, "GET")
self.assertEqual(self.app['last_request'].query_string,
"graph=%s" % self.client_kwargs['graph'].value)
self.assertEqual(self.app['last_request'].headers['Accept'],
"some/format")

async with self.client.get(format="some/format", graph=IRI("foo")) \
as response:
self.assertIsInstance(response, aiohttp.ClientResponse)
self.assertEqual(self.app['last_request'].method, "GET")
self.assertEqual(self.app['last_request'].query_string, "graph=foo")
self.assertEqual(self.app['last_request'].headers['Accept'],
"some/format")

@unittest_run_loop
async def test_put(self):
await self.client.put(b"", format="some/format")
self.assertEqual(self.app['last_request'].method, "PUT")
self.assertEqual(self.app['last_request'].query_string,
"graph=%s" % self.client_kwargs['graph'].value)
self.assertEqual(self.app['last_request'].headers['Content-Type'],
"some/format")

await self.client.put(b"", format="some/format", graph=IRI("foo"))
self.assertEqual(self.app['last_request'].query_string, "graph=foo")

@unittest_run_loop
async def test_delete(self):
await self.client.delete()
self.assertEqual(self.app['last_request'].method, "DELETE")
self.assertEqual(self.app['last_request'].query_string,
"graph=%s" % self.client_kwargs['graph'].value)

await self.client.delete(IRI("foo"))
self.assertEqual(self.app['last_request'].query_string, "graph=foo")

@unittest_run_loop
async def test_post(self):
await self.client.post(b"", format="some/format")
self.assertEqual(self.app['last_request'].method, "POST")
self.assertEqual(self.app['last_request'].query_string,
"graph=%s" % self.client_kwargs['graph'].value)
self.assertEqual(self.app['last_request'].headers['Content-Type'],
"some/format")

await self.client.post(b"", format="some/format", graph=IRI("foo"))
self.assertEqual(self.app['last_request'].query_string, "graph=foo")


class ClientCustomPrefixes(AioSPARQLTestCase):
client_kwargs = {
Expand Down

0 comments on commit 60196e9

Please sign in to comment.