Repository: astropy/pyvo
Pull request #220 (2020-03-05 16:42:17)

TJ's implementation for issue 212:  TAP examples
This appears to work for all the services I tried, which included those using XHTML or HTML.  Either way, it's just using the xml.etree.ElementTree parser, which is a standard library not a new dependency.  So I think this should be OK, right?   

Second question:  the response to /examples is optional.  This implementation returns a simple list of TAPService objects.  In the case that the response is 404 (allowed), it returns an empty list rather than None.  Is this what we want?  

(Sorry about the diffs in the notebook.  The format of the whole thing apparently had to be updated according to my Jupyter.)  

cbanek: Assigning milestone 1.1.  I think this looks good!  I'm going to ask if @funbaker has any thoughts on the XML parts, since I'm pretty clueless in that regard, I just follow the pattern.  I'm also not sure what the test failure is, but it doesn't look related?
stargaser: I think the test failure is due to astropy/astropy#9505 which might be pushed to astropy release 4.1, based on the discussion in the Astropy Slack astroquery channel.
funbaker: The XML Parser Stuff is fine.

I suspect that astropy PR for making the test fail as well.
Which needs to be fixed in the test itself i think (removing the `b`)
tomdonaldson: The test updates should be compatible with new and old versions of astropy.  Once the dust settles, Astropy 4.1+ will not put `bytes` objects in the results.  See this astroquery PR for an example of handling the versions:  https://github.com/astropy/astroquery/pull/1669 
funbaker: @tomdonaldson pyvo's tests test explicit for a byte string. hence i said it needs to be fixed in the pyvo tests.
tomdonaldson: @funbaker Yes, I totally agree.  My point was that the pyvo tests should be written to pass whether the Astropy version is <4.1 or >=4.1.
trjaffe: At my level of experience, I don't want to mess with other people's tests, so I guess I'll rebase when somebody else fixes that.   
funbaker: @trjaffe no worries, thats what code reviews are for.
bsipocz: Unrelated comment, but I saw the notebook for the diff here: Do you test the notebooks? If not then consider (not just here, but for the other notebooks as well) massaging the examples into code snippet into the narrative docs, so the doctesting is actually picking them up.
Having potentially outdated examples is a very painful situation, so try to avoid it as much as you can.
andamian: I've already fixed the tests astropy related tests in my PR - https://github.com/astropy/pyvo/pull/218. Just need review and approval to merge...
cbanek: Sorry this one fell off my radar, can we rebase and I think that should get the build to work?
trjaffe: > Sorry this one fell off my radar, can we rebase and I think that should get the build to work?

Don't I have to wait for PR #218 to be approved and merged?  I had understood that it has some fixes to the tests in it.   
andamian: @trjaffe - #218 merged into master and master is now passing. Please pull it and see if you can turn this green.

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 7d93e2a..6d28220 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,8 @@
 1.1 (unreleased)
 ================
 
+- Added TAP examples function. [#220]
+
 - Add default for UWS version. [#199]
 
 - Handle description of None when describing a TAP service's tables. [#197]
diff --git a/examples/notebooks/simple_service_discovery.ipynb b/examples/notebooks/simple_service_discovery.ipynb
index d9c806e..83190af 100644
--- a/examples/notebooks/simple_service_discovery.ipynb
+++ b/examples/notebooks/simple_service_discovery.ipynb
@@ -1,202 +1,275 @@
 {
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pyvo as vo"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The most common use of the registry is to find archives with \n",
+    "`VO data access services`, and the simplest way\n",
+    "to do this is to use the \n",
+    "`~pyvo.regsearch` function.  For example, to find data\n",
+    "collections that contain searchable X-ray images:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "services = vo.regsearch(servicetype='sia', waveband='x-ray')\n",
+    "len(services)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Aha! Perhaps you didn't realize there were that many.\n",
+    "What collections are these?"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for service in services:\n",
+    "    print(service.res_title)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "As you can gather, each record in the registry search results\n",
+    "represents a different service (in this case, an image service).\n",
+    "Included in the record is the all-important base URL for the service:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(services[0].access_url)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "However, it's not necessary to keep track of that URL because you can\n",
+    "now search that collection directly via the registry record:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "images = services[0].search(pos=(350.85, 58.815), size=0.25)\n",
+    "len(images)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Other types of services via the ``servicetype`` parameter:\n",
+    "\n",
+    "| set ``servicetype`` to... | ...to find:\n",
+    "| ------------------------- | -------------------------------------\n",
+    "| sia                       | Simple Image Access (SIA) services\n",
+    "| ssa                       | Simple Spectral Access (SSA) services\n",
+    "| scs                       | Simple Cone Search (SCS) services\n",
+    "| slap                      | Simple Line Access (SLA) services\n",
+    "| tap                       | Table Access Protocol (TAP) services\n",
+    "\n",
+    "For example, to find all known Cone Search services:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cats = vo.regsearch(servicetype='conesearch')\n",
+    "len(cats)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Wow, that's a lot of catalogs.  (Most of these are from the\n",
+    "`Vizier Catalog Archive <http://vizier.u-strasbg.fr/viz-bin/VizieR>`)\n",
+    "every Vizier catalog that includes a position is available as a Cone\n",
+    "Search service.)  For just catalogs related to blazars:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cats = vo.regsearch(keywords=['blazar'], servicetype='conesearch')\n",
+    "len(cats)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "How about blazars observed with Fermi?"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cats = vo.regsearch(keywords=['blazar', 'Fermi'], servicetype='conesearch')\n",
+    "len(cats)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Sometimes you may be looking for a particular catalog or image collections\n",
+    "that you already know exists, and you just need to learn the base URL\n",
+    "for the service.  The ``keywords`` parameter can be used to find it.\n",
+    "For example, suppose you want to get cutout images from the NRAO VLA\n",
+    "Sky Survey (NVSS):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "colls = vo.regsearch(keywords=[\"NVSS\"], servicetype='sia')\n",
+    "for coll in colls:\n",
+    "    print(coll.res_title)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "More powerful than a cone search is a generic TAP service.  These services optionally provide helpful information on how to use them.  First, let's see what services are available that serve NASA data:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "taps = vo.regsearch(servicetype='tap',keywords=['HEASARC'])\n",
+    "for tapsvc in taps:\n",
+    "    tapsvc.describe()\n",
+    "    print(\"---------\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Then we need to find out what tables each service serves, because each *may* server many.  "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "tables=taps[0].service.tables\n",
+    "for table in tables:\n",
+    "    table.describe()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "You can see example queries if the service offers them.  This .examples method returns a TAPQuery object that can be executed.  "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for example in taps[0].service.examples:\n",
+    "    print(example['QUERY'])\n",
+    "    result=example.execute()\n",
+    "    break\n",
+    "result.to_table()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "And for each table, you can find out the available columns:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(\"Columns={}\".format(sorted([k.name for k in tables['a1'].columns ])))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
  "metadata": {
-  "name": "",
-  "signature": "sha256:e5bd7bbcbb2f77cb4950888f893c03f6a26e81270b3d9c3d8e35af9c004f431e"
- },
- "nbformat": 3,
- "nbformat_minor": 0,
- "worksheets": [
-  {
-   "cells": [
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "import pyvo as vo"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "The most common use of the registry is to find archives with \n",
-      "`VO data access services`, and the simplest way\n",
-      "to do this is to use the \n",
-      "`~pyvo.regsearch` function.  For example, to find data\n",
-      "collections that contain searchable X-ray images:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "services = vo.regsearch(servicetype='sia', waveband='x-ray')\n",
-      "len(services)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Aha! Perhaps you didn't realize there were that many.\n",
-      "What collections are these?"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "for service in services:\n",
-      "    print(service.res_title)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "As you can gather, each record in the registry search results\n",
-      "represents a different service (in this case, an image service).\n",
-      "Included in the record is the all-important base URL for the service:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "print(services[0].access_url)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "However, it's not necessary to keep track of that URL because you can\n",
-      "now search that collection directly via the registry record:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "images = services[0].search(pos=(350.85, 58.815), size=0.25)\n",
-      "len(images)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Other types of services via the ``servicetype`` parameter:\n",
-      "\n",
-      "| set ``servicetype`` to... | ...to find:\n",
-      "| ------------------------- | -------------------------------------\n",
-      "| sia                       | Simple Image Access (SIA) services\n",
-      "| ssa                       | Simple Spectral Access (SSA) services\n",
-      "| scs                       | Simple Cone Search (SCS) services\n",
-      "| slap                      | Simple Line Access (SLA) services\n",
-      "| tap                       | Table Access Protocol (TAP) services\n",
-      "\n",
-      "For example, to find all known Cone Search services:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "cats = vo.regsearch(servicetype='conesearch')\n",
-      "len(cats)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Wow, that's a lot of catalogs.  (Most of these are from the\n",
-      "`Vizier Catalog Archive <http://vizier.u-strasbg.fr/viz-bin/VizieR>`)\n",
-      "every Vizier catalog that includes a position is available as a Cone\n",
-      "Search service.)  For just catalogs related to blazars:"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "cats = vo.regsearch(keywords=['blazar'], servicetype='conesearch')\n",
-      "len(cats)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "How about blazars observed with Fermi?"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "cats = vo.regsearch(keywords=['blazar', 'Fermi'], servicetype='conesearch')\n",
-      "len(cats)"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "markdown",
-     "metadata": {},
-     "source": [
-      "Sometimes you may be looking for a particular catalog or image collections\n",
-      "that you already know exists, and you just need to learn the base URL\n",
-      "for the service.  The ``keywords`` parameter can be used to find it.\n",
-      "For example, suppose you want to get cutout images from the NRAO VLA\n",
-      "Sky Survey (NVSS):"
-     ]
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [
-      "colls = vo.regsearch(keywords=[\"NVSS\"], servicetype='sia')\n",
-      "for coll in colls:\n",
-      "    print coll.res_title"
-     ],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    },
-    {
-     "cell_type": "code",
-     "collapsed": false,
-     "input": [],
-     "language": "python",
-     "metadata": {},
-     "outputs": []
-    }
-   ],
-   "metadata": {}
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.7"
   }
- ]
-}
\ No newline at end of file
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/pyvo/dal/tap.py b/pyvo/dal/tap.py
index e446fe8..3fe5786 100644
--- a/pyvo/dal/tap.py
+++ b/pyvo/dal/tap.py
@@ -23,6 +23,8 @@ from ..io.vosi import tapregext as tr
 
 from ..utils.formatting import para_format_desc
 from ..utils.http import use_session
+import xml.etree.ElementTree
+import io
 
 __all__ = [
     "search", "escape", "TAPService", "TAPQuery", "AsyncTAPJob", "TAPResults"]
@@ -95,6 +97,7 @@ class TAPService(DALService, AvailabilityMixin, CapabilityMixin):
     """
 
     _tables = None
+    _examples = None
 
     def __init__(self, baseurl, session=None):
         """
@@ -138,6 +141,33 @@ class TAPService(DALService, AvailabilityMixin, CapabilityMixin):
                 vosi.parse_tables(response.raw.read), tables_url)
         return self._tables
 
+    @property
+    def examples(self):
+        """
+        returns examples as a list of TAPQuery objects
+        """
+        if self._examples is None:
+            examples_url = '{}/examples'.format(self.baseurl)
+
+            response = self._session.get(examples_url, stream=True)
+            if response.status_code == 404:
+                return []
+
+            try:
+                response.raise_for_status()
+            except requests.RequestException as ex:
+                raise DALServiceError.from_except(ex, examples_url)
+
+            try:
+                root = xml.etree.ElementTree.parse(io.BytesIO(response.content)).getroot()
+                exampleElements = root.findall('.//*[@property="query"]')
+            except Exception as ex:
+                raise DALServiceError.from_except(ex, examples_url)
+
+            self._examples = [TAPQuery(self.baseurl, example.text) for example in exampleElements]
+
+        return self._examples
+
     @property
     def maxrec(self):
         """


Test patch:
diff --git a/pyvo/dal/tests/data/tap/examples.htm b/pyvo/dal/tests/data/tap/examples.htm
new file mode 100644
index 0000000..c4f8597
--- /dev/null
+++ b/pyvo/dal/tests/data/tap/examples.htm
@@ -0,0 +1,1 @@
+<!DOCTYPE html  PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN"  "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-2.dtd"><!-- used by taprender.TAPExamples --><html version="XHTML+RDFa 1.1" xmlns="http://www.w3.org/1999/xhtml">  <head>    <title>Examples queries for HEASARC's TAP service</title>  </head>  <body class="container" vocab="ivo://ivoa.net/std/DALI-examples#">    <div id="body">      <a name="body"></a>      <h1>Examples for the TAP service at HEASARC</h1>      <div id="exampleslist">         <div typeof="example" id="QueryContainsPointCircle" resource="#QueryContainsPointCircle">          <h2 property="name">Simple geometric query on rosmaster with circle and point</h2>          <p>            The Table Access Protocol Service at HEASARC allow for simple          geometric queries.  For example, this query searches for observations in the rostmaster          catalog within a circle of radius 1 degree of the          coordinates (ra,dec)=(50,-85) -- i.e., basically a cone          search -- and an exposure longer than 10000 seconds:            </p>          <pre property="query">            SELECT * FROM rosmaster                      WHERE exposure > 10000 and                            1=CONTAINS(POINT('ICRS', ra, dec),CIRCLE('ICRS', 50, -85, 1))          </pre>        </div>         <div typeof="example" id="CrossMatch" resource="#CrossMatch">          <h2 property="name">Simple geometric query on rosmaster with circle and point</h2>          <p>            The Table Access Protocol Service at HEASARC allow for simple          geometric and cross-match queries.  For example, this query searches for observations common to the rostmaster          catalog AND the chanmaster catalog with a minimum exposure of 10ks.          </p>          <pre property="query">	   SELECT * FROM rosmaster as ros	            INNER JOIN chanmaster as chan	                ON ros.name = chan.name	            WHERE ros.exposure > 10000 and chan.exposure > 10000	            ORDER by ros.exposure	             </pre>        </div>	<div typeof="example" id="QueryCirclesIntersect" resource="#QueryCirclesIntersect">          <h2 property="name">Simple geometric query on rosmaster with intersects, circle, point</h2>          <p>            The Table Access Protocol Service at HEASARC allow for simple          geometric queries.  For example, this (slow) query searches for observations in the rostmaster          catalog where a circle of radius 1 degree of the          coordinates (ra,dec)=(50,-85) intersects with a circle of 1          degree radius around the center of the pointing:            </p>          <pre property="query">            SELECT * FROM rosmaster                      WHERE 1=INTERSECTS(CIRCLE('ICRS', ra, dec,1),CIRCLE('ICRS', 50, -85, 1))          </pre>        </div>	        <div typeof="example" id="QueryPolygon" resource="#QueryPolygon">          <h2 property="name">Simple geometric query on rosmaster with polygon</h2>          <p>            The Table Access Protocol Service at HEASARC allow for simple          geometric queries.  For example, this query searches for observations in the rostmaster          catalog where a pointing lies with a user-defined polygon, in this case a triangle with	  vertices (-5,-5), (5,-5), and (0,5)):            </p>          <pre property="query">            SELECT * FROM rosmaster                      WHERE  exposure > 10000 AND                             1=CONTAINS(POINT('ICRS', ra, dec),POLYGON('ICRS', -5, -5, 5, -5, 0, 5))          </pre>        </div>		        <div typeof="example" id="QueryDistance" resource="#QueryDistance">          <h2 property="name">Simple geometric query on rosmaster, chanmaster with join on distance</h2>          <p>            The Table Access Protocol Service at HEASARC allow for simple          geometric queries.  For example, this query computes the distance between rostmaster observations	  and a given point, selecting those observations near it, and ordering the result by that distance:          </p>          <pre property="query">          SELECT DISTANCE(	      POINT('ICRS', ra, dec),              POINT('ICRS', 266.41683, -29.00781)) AS dist, *	  FROM rosmaster	  WHERE 1=CONTAINS(	      POINT('ICRS', ra, dec),	      CIRCLE('ICRS', 266.41683, -29.00781, 1))	  ORDER BY dist ASC          </pre>        </div>	      </div>    </div>  </body></html>
\ No newline at end of file
diff --git a/pyvo/dal/tests/setup_package.py b/pyvo/dal/tests/setup_package.py
index 903ea19..6a5c016 100644
--- a/pyvo/dal/tests/setup_package.py
+++ b/pyvo/dal/tests/setup_package.py
@@ -9,6 +9,7 @@ def get_package_data():
         os.path.join('data/querydata', '*.xml'),
         os.path.join('data/querydata', '*.xml'),
         os.path.join('data/tap', '*.xml'),
+        os.path.join('data/tap', '*.htm'),
         os.path.join('data/scs', '*.xml'),
         os.path.join('data/sia', '*.xml'),
         os.path.join('data/sia2', '*.xml'),
diff --git a/pyvo/dal/tests/test_tap.py b/pyvo/dal/tests/test_tap.py
index f9f51ae..d976abf 100644
--- a/pyvo/dal/tests/test_tap.py
+++ b/pyvo/dal/tests/test_tap.py
@@ -298,6 +298,17 @@ def tables(mocker):
         yield matchers
 
 
+@pytest.fixture()
+def examples(mocker):
+    def callback_examplesXHTML(request, context):
+        return get_pkg_data_contents('data/tap/examples.htm')
+
+    with mocker.register_uri(
+        'GET', 'http://example.com/tap/examples', content=callback_examplesXHTML
+    ) as matcher:
+        yield matcher
+
+
 @pytest.fixture()
 def capabilities(mocker):
     def callback(request, context):
@@ -349,6 +360,16 @@ class TestTAPService:
         table1, table2 = list(tables)
         self._test_tables(table1, table2)
 
+    def _test_examples(self, exampleXHTML):
+        assert "SELECT * FROM rosmaster" in exampleXHTML[0]['QUERY']
+
+    @pytest.mark.usefixtures('examples')
+    def test_examples(self):
+        service = TAPService('http://example.com/tap')
+        examples = service.examples
+
+        self._test_examples(examples)
+
     @pytest.mark.usefixtures('capabilities')
     def test_maxrec(self):
         service = TAPService('http://example.com/tap')