├── .gitignore ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _themes │ └── armstrong │ │ ├── LICENSE │ │ ├── README │ │ ├── layout.html │ │ ├── rtd-themes.conf │ │ ├── static │ │ └── rtd.css_t │ │ └── theme.conf ├── conf.py ├── index.rst └── make.bat ├── rdflib_web ├── __init__.py ├── bookdb.py ├── caches.py ├── endpoint.py ├── generic_endpoint.py ├── htmlresults.py ├── lod.py ├── mimeutils.py ├── static │ ├── jquery.min.js │ ├── jquery.tablesorter.min.js │ ├── lod.js │ ├── star_active.png │ ├── star_active.svg │ ├── star_inactive.png │ ├── star_inactive.svg │ └── style.css └── templates │ ├── base.html │ ├── class.html │ ├── classes.html │ ├── index.html │ ├── instances.html │ ├── lodbase.html │ ├── lodindex.html │ ├── lodpage.html │ ├── picked.html │ ├── properties.html │ ├── property.html │ ├── results.html │ ├── search.html │ └── utils.html ├── run_tests.py ├── setup.cfg ├── setup.py └── test ├── endpoint_protocol ├── README.md ├── test_graphstore_w3c.md ├── test_graphstore_with_ConjunctiveGraph.md └── test_graphstore_with_graph.md ├── test_endpoint_protocol.py ├── test_lod_app.py └── test_served_sparql_store.py /.gitignore: -------------------------------------------------------------------------------- 1 | *pyc 2 | *~ 3 | *egg-info 4 | build 5 | dist 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LICENSE AGREEMENT FOR RDFLIB 2 | ------------------------------------------------ 3 | Copyright (c) 2002-2017, RDFLib Team 4 | See CONTRIBUTORS and http://github.com/RDFLib/rdflib 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above 15 | copyright notice, this list of conditions and the following 16 | disclaimer in the documentation and/or other materials provided 17 | with the distribution. 18 | 19 | * Neither the name of Daniel Krech nor the names of its 20 | contributors may be used to endorse or promote products derived 21 | from this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 31 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | rdflib-web 3 | ========== 4 | Two RDFLib web-apps on top of the Flask web-microframework: 5 | * a SPARQL endpoint app implementing the SPARQL 1.0 Protocol 6 | * a Linked Open Data app for publishing resources as HTML pages 7 | 8 | Each app is available as a commandline-script, or as a Flask Blueprint 9 | for embedding in your own application. 10 | 11 | Documentation on ReadTheDocs: http://rdflib-web.readthedocs.org/en/latest/ 12 | 13 | Installation 14 | ------------ 15 | 16 | Stable version: 17 | ```bash 18 | pip install rdflib-web 19 | ``` 20 | 21 | or most recent: 22 | 23 | ```bash 24 | pip install https://github.com/RDFLib/rdflib-web/archive/master.zip 25 | ``` 26 | 27 | Requirements 28 | ------------ 29 | 30 | These are installed automatically if you install with pip 31 | 32 | * For the Web-apps: Flask, http://flask.pocoo.org/ 33 | * (which in turn requires Jinja2 and Werkzeug) 34 | * For correct content-negotiation: mimeparse (fallback without conneg) 35 | -------------------------------------------------------------------------------- /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/rdflib-web.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rdflib-web.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/rdflib-web" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rdflib-web" 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/_themes/armstrong/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Bay Citizen & Texas Tribune 2 | 3 | Original ReadTheDocs.org code 4 | Copyright (c) 2010 Charles Leifer, Eric Holscher, Bobby Grace 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /docs/_themes/armstrong/README: -------------------------------------------------------------------------------- 1 | This is the Armstrong Sphinx theme from https://github.com/armstrong/armstrong_sphinx 2 | 3 | Used under BSD license. 4 | -------------------------------------------------------------------------------- /docs/_themes/armstrong/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | 3 | {% set script_files = script_files + [pathto("_static/searchtools.js", 1)] %} 4 | 5 | {% block htmltitle %} 6 | {{ super() }} 7 | 8 | 9 | 10 | {% endblock %} 11 | 12 | {% block footer %} 13 | 31 | 32 | 33 | {% if theme_analytics_code %} 34 | 35 | 46 | {% endif %} 47 | 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /docs/_themes/armstrong/rtd-themes.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = default 3 | stylesheet = rtd.css 4 | pygment_style = default 5 | show_sphinx = False 6 | 7 | [options] 8 | show_rtd = True 9 | 10 | white = #ffffff 11 | almost_white = #f8f8f8 12 | barely_white = #f2f2f2 13 | dirty_white = #eeeeee 14 | almost_dirty_white = #e6e6e6 15 | dirtier_white = #dddddd 16 | lighter_gray = #cccccc 17 | gray_a = #aaaaaa 18 | gray_9 = #999999 19 | light_gray = #888888 20 | gray_7 = #777777 21 | gray = #666666 22 | dark_gray = #444444 23 | gray_2 = #222222 24 | black = #111111 25 | light_color = #e8ecef 26 | light_medium_color = #DDEAF0 27 | medium_color = #8ca1af 28 | medium_color_link = #86989b 29 | medium_color_link_hover = #a6b8bb 30 | dark_color = #465158 31 | 32 | h1 = #000000 33 | h2 = #465158 34 | h3 = #6c818f 35 | 36 | link_color = #444444 37 | link_color_decoration = #CCCCCC 38 | 39 | medium_color_hover = #697983 40 | green_highlight = #8ecc4c 41 | 42 | 43 | positive_dark = #609060 44 | positive_medium = #70a070 45 | positive_light = #e9ffe9 46 | 47 | negative_dark = #900000 48 | negative_medium = #b04040 49 | negative_light = #ffe9e9 50 | negative_text = #c60f0f 51 | 52 | ruler = #abc 53 | 54 | viewcode_bg = #f4debf 55 | viewcode_border = #ac9 56 | 57 | highlight = #ffe080 58 | 59 | code_background = #eeeeee 60 | 61 | background = #465158 62 | background_link = #ffffff 63 | background_link_half = #ffffff 64 | background_text = #eeeeee 65 | background_text_link = #86989b 66 | -------------------------------------------------------------------------------- /docs/_themes/armstrong/static/rtd.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * rtd.css 3 | * ~~~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- sphinxdoc theme. Originally created by 6 | * Armin Ronacher for Werkzeug. 7 | * 8 | * Customized for ReadTheDocs by Eric Pierce & Eric Holscher 9 | * 10 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 11 | * :license: BSD, see LICENSE for details. 12 | * 13 | */ 14 | 15 | /* RTD colors 16 | * light blue: {{ theme_light_color }} 17 | * medium blue: {{ theme_medium_color }} 18 | * dark blue: {{ theme_dark_color }} 19 | * dark grey: {{ theme_grey_color }} 20 | * 21 | * medium blue hover: {{ theme_medium_color_hover }}; 22 | * green highlight: {{ theme_green_highlight }} 23 | * light blue (project bar): {{ theme_light_color }} 24 | */ 25 | 26 | @import url("basic.css"); 27 | 28 | /* PAGE LAYOUT -------------------------------------------------------------- */ 29 | 30 | body { 31 | font: 100%/1.5 "ff-meta-web-pro-1","ff-meta-web-pro-2",Arial,"Helvetica Neue",sans-serif; 32 | text-align: center; 33 | color: black; 34 | background-color: {{ theme_background }}; 35 | padding: 0; 36 | margin: 0; 37 | } 38 | 39 | div.document { 40 | text-align: left; 41 | background-color: {{ theme_light_color }}; 42 | } 43 | 44 | div.bodywrapper { 45 | background-color: {{ theme_white }}; 46 | border-left: 1px solid {{ theme_lighter_gray }}; 47 | border-bottom: 1px solid {{ theme_lighter_gray }}; 48 | margin: 0 0 0 16em; 49 | } 50 | 51 | div.body { 52 | margin: 0; 53 | padding: 0.5em 1.3em; 54 | max-width: 55em; 55 | min-width: 20em; 56 | } 57 | 58 | div.related { 59 | font-size: 1em; 60 | background-color: {{ theme_background }}; 61 | } 62 | 63 | div.documentwrapper { 64 | float: left; 65 | width: 100%; 66 | background-color: {{ theme_light_color }}; 67 | } 68 | 69 | 70 | /* HEADINGS --------------------------------------------------------------- */ 71 | 72 | h1 { 73 | margin: 0; 74 | padding: 0.7em 0 0.3em 0; 75 | font-size: 1.5em; 76 | line-height: 1.15; 77 | color: {{ theme_h1 }}; 78 | clear: both; 79 | } 80 | 81 | h2 { 82 | margin: 2em 0 0.2em 0; 83 | font-size: 1.35em; 84 | padding: 0; 85 | color: {{ theme_h2 }}; 86 | } 87 | 88 | h3 { 89 | margin: 1em 0 -0.3em 0; 90 | font-size: 1.2em; 91 | color: {{ theme_h3 }}; 92 | } 93 | 94 | div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { 95 | color: black; 96 | } 97 | 98 | h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { 99 | display: none; 100 | margin: 0 0 0 0.3em; 101 | padding: 0 0.2em 0 0.2em; 102 | color: {{ theme_gray_a }} !important; 103 | } 104 | 105 | h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, 106 | h5:hover a.anchor, h6:hover a.anchor { 107 | display: inline; 108 | } 109 | 110 | h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, 111 | h5 a.anchor:hover, h6 a.anchor:hover { 112 | color: {{ theme_gray_7 }}; 113 | background-color: {{ theme_dirty_white }}; 114 | } 115 | 116 | 117 | /* LINKS ------------------------------------------------------------------ */ 118 | 119 | /* Normal links get a pseudo-underline */ 120 | a { 121 | color: {{ theme_link_color }}; 122 | text-decoration: none; 123 | border-bottom: 1px solid {{ theme_link_color_decoration }}; 124 | } 125 | 126 | /* Links in sidebar, TOC, index trees and tables have no underline */ 127 | .sphinxsidebar a, 128 | .toctree-wrapper a, 129 | .indextable a, 130 | #indices-and-tables a { 131 | color: {{ theme_dark_gray }}; 132 | text-decoration: none; 133 | border-bottom: none; 134 | } 135 | 136 | /* Most links get an underline-effect when hovered */ 137 | a:hover, 138 | div.toctree-wrapper a:hover, 139 | .indextable a:hover, 140 | #indices-and-tables a:hover { 141 | color: {{ theme_black }}; 142 | text-decoration: none; 143 | border-bottom: 1px solid {{ theme_black }}; 144 | } 145 | 146 | /* Footer links */ 147 | div.footer a { 148 | color: {{ theme_background_text_link }}; 149 | text-decoration: none; 150 | border: none; 151 | } 152 | div.footer a:hover { 153 | color: {{ theme_medium_color_link_hover }}; 154 | text-decoration: underline; 155 | border: none; 156 | } 157 | 158 | /* Permalink anchor (subtle grey with a red hover) */ 159 | div.body a.headerlink { 160 | color: {{ theme_lighter_gray }}; 161 | font-size: 1em; 162 | margin-left: 6px; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | border: none; 166 | } 167 | div.body a.headerlink:hover { 168 | color: {{ theme_negative_text }}; 169 | border: none; 170 | } 171 | 172 | 173 | /* NAVIGATION BAR --------------------------------------------------------- */ 174 | 175 | div.related ul { 176 | height: 2.5em; 177 | } 178 | 179 | div.related ul li { 180 | margin: 0; 181 | padding: 0.65em 0; 182 | float: left; 183 | display: block; 184 | color: {{ theme_background_link_half }}; /* For the >> separators */ 185 | font-size: 0.8em; 186 | } 187 | 188 | div.related ul li.right { 189 | float: right; 190 | margin-right: 5px; 191 | color: transparent; /* Hide the | separators */ 192 | } 193 | 194 | /* "Breadcrumb" links in nav bar */ 195 | div.related ul li a { 196 | order: none; 197 | background-color: inherit; 198 | font-weight: bold; 199 | margin: 6px 0 6px 4px; 200 | line-height: 1.75em; 201 | color: {{ theme_background_link }}; 202 | text-shadow: 0 1px rgba(0, 0, 0, 0.5); 203 | padding: 0.4em 0.8em; 204 | border: none; 205 | border-radius: 3px; 206 | } 207 | /* previous / next / modules / index links look more like buttons */ 208 | div.related ul li.right a { 209 | margin: 0.375em 0; 210 | background-color: {{ theme_medium_color_hover }}; 211 | text-shadow: 0 1px rgba(0, 0, 0, 0.5); 212 | border-radius: 3px; 213 | -webkit-border-radius: 3px; 214 | -moz-border-radius: 3px; 215 | } 216 | /* All navbar links light up as buttons when hovered */ 217 | div.related ul li a:hover { 218 | background-color: {{ theme_medium_color }}; 219 | color: {{ theme_white }}; 220 | text-decoration: none; 221 | border-radius: 3px; 222 | -webkit-border-radius: 3px; 223 | -moz-border-radius: 3px; 224 | } 225 | /* Take extra precautions for tt within links */ 226 | a tt, 227 | div.related ul li a tt { 228 | background: inherit !important; 229 | color: inherit !important; 230 | } 231 | 232 | 233 | /* SIDEBAR ---------------------------------------------------------------- */ 234 | 235 | div.sphinxsidebarwrapper { 236 | padding: 0; 237 | } 238 | 239 | div.sphinxsidebar { 240 | margin: 0; 241 | margin-left: -100%; 242 | float: left; 243 | top: 3em; 244 | left: 0; 245 | padding: 0 1em; 246 | width: 14em; 247 | font-size: 1em; 248 | text-align: left; 249 | background-color: {{ theme_light_color }}; 250 | } 251 | 252 | div.sphinxsidebar img { 253 | max-width: 12em; 254 | } 255 | 256 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 257 | margin: 1.2em 0 0.3em 0; 258 | font-size: 1em; 259 | padding: 0; 260 | color: {{ theme_gray_2 }}; 261 | font-family: "ff-meta-web-pro-1", "ff-meta-web-pro-2", "Arial", "Helvetica Neue", sans-serif; 262 | } 263 | 264 | div.sphinxsidebar h3 a { 265 | color: {{ theme_grey_color }}; 266 | } 267 | 268 | div.sphinxsidebar ul, 269 | div.sphinxsidebar p { 270 | margin-top: 0; 271 | padding-left: 0; 272 | line-height: 130%; 273 | background-color: {{ theme_light_color }}; 274 | } 275 | 276 | /* No bullets for nested lists, but a little extra indentation */ 277 | div.sphinxsidebar ul ul { 278 | list-style-type: none; 279 | margin-left: 1.5em; 280 | padding: 0; 281 | } 282 | 283 | /* A little top/bottom padding to prevent adjacent links' borders 284 | * from overlapping each other */ 285 | div.sphinxsidebar ul li { 286 | padding: 1px 0; 287 | } 288 | 289 | /* A little left-padding to make these align with the ULs */ 290 | div.sphinxsidebar p.topless { 291 | padding-left: 0 0 0 1em; 292 | } 293 | 294 | /* Make these into hidden one-liners */ 295 | div.sphinxsidebar ul li, 296 | div.sphinxsidebar p.topless { 297 | white-space: nowrap; 298 | overflow: hidden; 299 | } 300 | /* ...which become visible when hovered */ 301 | div.sphinxsidebar ul li:hover, 302 | div.sphinxsidebar p.topless:hover { 303 | overflow: visible; 304 | } 305 | 306 | /* Search text box and "Go" button */ 307 | #searchbox { 308 | margin-top: 2em; 309 | margin-bottom: 1em; 310 | background: {{ theme_dirtier_white }}; 311 | padding: 0.5em; 312 | border-radius: 6px; 313 | -moz-border-radius: 6px; 314 | -webkit-border-radius: 6px; 315 | } 316 | #searchbox h3 { 317 | margin-top: 0; 318 | } 319 | 320 | /* Make search box and button abut and have a border */ 321 | input, 322 | div.sphinxsidebar input { 323 | border: 1px solid {{ theme_gray_9 }}; 324 | float: left; 325 | } 326 | 327 | /* Search textbox */ 328 | input[type="text"] { 329 | margin: 0; 330 | padding: 0 3px; 331 | height: 20px; 332 | width: 144px; 333 | border-top-left-radius: 3px; 334 | border-bottom-left-radius: 3px; 335 | -moz-border-radius-topleft: 3px; 336 | -moz-border-radius-bottomleft: 3px; 337 | -webkit-border-top-left-radius: 3px; 338 | -webkit-border-bottom-left-radius: 3px; 339 | } 340 | /* Search button */ 341 | input[type="submit"] { 342 | margin: 0 0 0 -1px; /* -1px prevents a double-border with textbox */ 343 | height: 22px; 344 | color: {{ theme_dark_gray }}; 345 | background-color: {{ theme_light_color }}; 346 | padding: 1px 4px; 347 | font-weight: bold; 348 | border-top-right-radius: 3px; 349 | border-bottom-right-radius: 3px; 350 | -moz-border-radius-topright: 3px; 351 | -moz-border-radius-bottomright: 3px; 352 | -webkit-border-top-right-radius: 3px; 353 | -webkit-border-bottom-right-radius: 3px; 354 | } 355 | input[type="submit"]:hover { 356 | color: {{ theme_white }}; 357 | background-color: {{ theme_green_highlight }}; 358 | } 359 | 360 | div.sphinxsidebar p.searchtip { 361 | clear: both; 362 | padding: 0.5em 0 0 0; 363 | background: {{ theme_dirtier_white }}; 364 | color: {{ theme_gray }}; 365 | font-size: 0.9em; 366 | } 367 | 368 | /* Sidebar links are unusual */ 369 | div.sphinxsidebar li a, 370 | div.sphinxsidebar p a { 371 | background: {{ theme_light_color }}; /* In case links overlap main content */ 372 | border-radius: 3px; 373 | -moz-border-radius: 3px; 374 | -webkit-border-radius: 3px; 375 | border: 1px solid transparent; /* To prevent things jumping around on hover */ 376 | padding: 0 5px 0 5px; 377 | } 378 | div.sphinxsidebar li a:hover, 379 | div.sphinxsidebar p a:hover { 380 | color: {{ theme_black }}; 381 | text-decoration: none; 382 | border: 1px solid {{ theme_light_gray }}; 383 | } 384 | 385 | /* Tweak any link appearing in a heading */ 386 | div.sphinxsidebar h3 a { 387 | } 388 | 389 | 390 | 391 | 392 | /* OTHER STUFF ------------------------------------------------------------ */ 393 | 394 | cite, code, tt { 395 | font-family: 'Consolas', 'Deja Vu Sans Mono', 396 | 'Bitstream Vera Sans Mono', monospace; 397 | font-size: 0.95em; 398 | letter-spacing: 0.01em; 399 | } 400 | 401 | tt { 402 | background-color: {{ theme_code_background }}; 403 | color: {{ theme_dark_gray }}; 404 | } 405 | 406 | tt.descname, tt.descclassname, tt.xref { 407 | border: 0; 408 | } 409 | 410 | hr { 411 | border: 1px solid {{ theme_ruler }}; 412 | margin: 2em; 413 | } 414 | 415 | pre, #_fontwidthtest { 416 | font-family: 'Consolas', 'Deja Vu Sans Mono', 417 | 'Bitstream Vera Sans Mono', monospace; 418 | margin: 1em 2em; 419 | font-size: 0.95em; 420 | letter-spacing: 0.015em; 421 | line-height: 120%; 422 | padding: 0.5em; 423 | border: 1px solid {{ theme_lighter_gray }}; 424 | background-color: {{ theme_code_background }}; 425 | border-radius: 6px; 426 | -moz-border-radius: 6px; 427 | -webkit-border-radius: 6px; 428 | } 429 | 430 | pre a { 431 | color: inherit; 432 | text-decoration: underline; 433 | } 434 | 435 | td.linenos pre { 436 | padding: 0.5em 0; 437 | } 438 | 439 | div.quotebar { 440 | background-color: {{ theme_almost_white }}; 441 | max-width: 250px; 442 | float: right; 443 | padding: 2px 7px; 444 | border: 1px solid {{ theme_lighter_gray }}; 445 | } 446 | 447 | div.topic { 448 | background-color: {{ theme_almost_white }}; 449 | } 450 | 451 | table { 452 | border-collapse: collapse; 453 | margin: 0 -0.5em 0 0; 454 | } 455 | 456 | table td, table th { 457 | padding: 0.2em 0.5em 0.2em 0.5em; 458 | } 459 | 460 | 461 | /* ADMONITIONS AND WARNINGS ------------------------------------------------- */ 462 | 463 | /* Shared by admonitions, warnings and sidebars */ 464 | div.admonition, 465 | div.warning, 466 | div.sidebar { 467 | font-size: 0.9em; 468 | margin: 2em; 469 | padding: 0; 470 | /* 471 | border-radius: 6px; 472 | -moz-border-radius: 6px; 473 | -webkit-border-radius: 6px; 474 | */ 475 | } 476 | div.admonition p, 477 | div.warning p, 478 | div.sidebar p { 479 | margin: 0.5em 1em 0.5em 1em; 480 | padding: 0; 481 | } 482 | div.admonition pre, 483 | div.warning pre, 484 | div.sidebar pre { 485 | margin: 0.4em 1em 0.4em 1em; 486 | } 487 | div.admonition p.admonition-title, 488 | div.warning p.admonition-title, 489 | div.sidebar p.sidebar-title { 490 | margin: 0; 491 | padding: 0.1em 0 0.1em 0.5em; 492 | color: white; 493 | font-weight: bold; 494 | font-size: 1.1em; 495 | text-shadow: 0 1px rgba(0, 0, 0, 0.5); 496 | } 497 | div.admonition ul, div.admonition ol, 498 | div.warning ul, div.warning ol, 499 | div.sidebar ul, div.sidebar ol { 500 | margin: 0.1em 0.5em 0.5em 3em; 501 | padding: 0; 502 | } 503 | 504 | 505 | /* Admonitions and sidebars only */ 506 | div.admonition, div.sidebar { 507 | border: 1px solid {{ theme_positive_dark }}; 508 | background-color: {{ theme_positive_light }}; 509 | } 510 | div.admonition p.admonition-title, 511 | div.sidebar p.sidebar-title { 512 | background-color: {{ theme_positive_medium }}; 513 | border-bottom: 1px solid {{ theme_positive_dark }}; 514 | } 515 | 516 | 517 | /* Warnings only */ 518 | div.warning { 519 | border: 1px solid {{ theme_negative_dark }}; 520 | background-color: {{ theme_negative_light }}; 521 | } 522 | div.warning p.admonition-title { 523 | background-color: {{ theme_negative_medium }}; 524 | border-bottom: 1px solid {{ theme_negative_dark }}; 525 | } 526 | 527 | 528 | /* Sidebars only */ 529 | div.sidebar { 530 | max-width: 200px; 531 | } 532 | 533 | 534 | 535 | div.versioninfo { 536 | margin: 1em 0 0 0; 537 | border: 1px solid {{ theme_lighter_gray }}; 538 | background-color: {{ theme_light_medium_color }}; 539 | padding: 8px; 540 | line-height: 1.3em; 541 | font-size: 0.9em; 542 | } 543 | 544 | .viewcode-back { 545 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 546 | 'Verdana', sans-serif; 547 | } 548 | 549 | div.viewcode-block:target { 550 | background-color: {{ theme_viewcode_bg }}; 551 | border-top: 1px solid {{ theme_viewcode_border }}; 552 | border-bottom: 1px solid {{ theme_viewcode_border }}; 553 | } 554 | 555 | dl { 556 | margin: 1em 0 2.5em 0; 557 | } 558 | 559 | /* Highlight target when you click an internal link */ 560 | dt:target { 561 | background: {{ theme_highlight }}; 562 | } 563 | /* Don't highlight whole divs */ 564 | div.highlight { 565 | background: transparent; 566 | } 567 | /* But do highlight spans (so search results can be highlighted) */ 568 | span.highlight { 569 | background: {{ theme_highlight }}; 570 | } 571 | 572 | div.footer { 573 | background-color: {{ theme_background }}; 574 | color: {{ theme_background_text }}; 575 | padding: 0 2em 2em 2em; 576 | clear: both; 577 | font-size: 0.8em; 578 | text-align: center; 579 | } 580 | 581 | p { 582 | margin: 0.8em 0 0.5em 0; 583 | } 584 | 585 | .section p img { 586 | margin: 1em 2em; 587 | } 588 | 589 | 590 | /* MOBILE LAYOUT -------------------------------------------------------------- */ 591 | 592 | @media screen and (max-width: 600px) { 593 | 594 | h1, h2, h3, h4, h5 { 595 | position: relative; 596 | } 597 | 598 | ul { 599 | padding-left: 1.75em; 600 | } 601 | 602 | div.bodywrapper a.headerlink, #indices-and-tables h1 a { 603 | color: {{ theme_almost_dirty_white }}; 604 | font-size: 80%; 605 | float: right; 606 | line-height: 1.8; 607 | position: absolute; 608 | right: -0.7em; 609 | visibility: inherit; 610 | } 611 | 612 | div.bodywrapper h1 a.headerlink, #indices-and-tables h1 a { 613 | line-height: 1.5; 614 | } 615 | 616 | pre { 617 | font-size: 0.7em; 618 | overflow: auto; 619 | word-wrap: break-word; 620 | white-space: pre-wrap; 621 | } 622 | 623 | div.related ul { 624 | height: 2.5em; 625 | padding: 0; 626 | text-align: left; 627 | } 628 | 629 | div.related ul li { 630 | clear: both; 631 | color: {{ theme_dark_color }}; 632 | padding: 0.2em 0; 633 | } 634 | 635 | div.related ul li:last-child { 636 | border-bottom: 1px dotted {{ theme_medium_color }}; 637 | padding-bottom: 0.4em; 638 | margin-bottom: 1em; 639 | width: 100%; 640 | } 641 | 642 | div.related ul li a { 643 | color: {{ theme_dark_color }}; 644 | padding-right: 0; 645 | } 646 | 647 | div.related ul li a:hover { 648 | background: inherit; 649 | color: inherit; 650 | } 651 | 652 | div.related ul li.right { 653 | clear: none; 654 | padding: 0.65em 0; 655 | margin-bottom: 0.5em; 656 | } 657 | 658 | div.related ul li.right a { 659 | color: {{ theme_white }}; 660 | padding-right: 0.8em; 661 | } 662 | 663 | div.related ul li.right a:hover { 664 | background-color: {{ theme_medium_color }}; 665 | } 666 | 667 | div.body { 668 | clear: both; 669 | min-width: 0; 670 | word-wrap: break-word; 671 | } 672 | 673 | div.bodywrapper { 674 | margin: 0 0 0 0; 675 | } 676 | 677 | div.sphinxsidebar { 678 | float: none; 679 | margin: 0; 680 | width: auto; 681 | } 682 | 683 | div.sphinxsidebar input[type="text"] { 684 | height: 2em; 685 | line-height: 2em; 686 | width: 70%; 687 | } 688 | 689 | div.sphinxsidebar input[type="submit"] { 690 | height: 2em; 691 | margin-left: 0.5em; 692 | width: 20%; 693 | } 694 | 695 | div.sphinxsidebar p.searchtip { 696 | background: inherit; 697 | margin-bottom: 1em; 698 | } 699 | 700 | div.sphinxsidebar ul li, div.sphinxsidebar p.topless { 701 | white-space: normal; 702 | } 703 | 704 | .bodywrapper img { 705 | display: block; 706 | margin-left: auto; 707 | margin-right: auto; 708 | max-width: 100%; 709 | } 710 | 711 | div.documentwrapper { 712 | float: none; 713 | } 714 | 715 | div.admonition, div.warning, pre, blockquote { 716 | margin-left: 0em; 717 | margin-right: 0em; 718 | } 719 | 720 | .body p img { 721 | margin: 0; 722 | } 723 | 724 | #searchbox { 725 | background: transparent; 726 | } 727 | 728 | .related:not(:first-child) li { 729 | display: none; 730 | } 731 | 732 | .related:not(:first-child) li.right { 733 | display: block; 734 | } 735 | 736 | div.footer { 737 | padding: 1em; 738 | } 739 | 740 | .rtd_doc_footer .badge { 741 | float: none; 742 | margin: 1em auto; 743 | position: static; 744 | } 745 | 746 | .rtd_doc_footer .badge.revsys-inline { 747 | margin-right: auto; 748 | margin-bottom: 2em; 749 | } 750 | 751 | table.indextable { 752 | display: block; 753 | width: auto; 754 | } 755 | 756 | .indextable tr { 757 | display: block; 758 | } 759 | 760 | .indextable td { 761 | display: block; 762 | padding: 0; 763 | width: auto !important; 764 | } 765 | 766 | .indextable td dt { 767 | margin: 1em 0; 768 | } 769 | 770 | ul.search { 771 | margin-left: 0.25em; 772 | } 773 | 774 | ul.search li div.context { 775 | font-size: 90%; 776 | line-height: 1.1; 777 | margin-bottom: 1; 778 | margin-left: 0; 779 | } 780 | 781 | } 782 | -------------------------------------------------------------------------------- /docs/_themes/armstrong/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = default 3 | stylesheet = rtd.css 4 | pygment_style = default 5 | show_sphinx = False 6 | 7 | [options] 8 | show_rtd = True 9 | 10 | white = #ffffff 11 | almost_white = #f8f8f8 12 | barely_white = #f2f2f2 13 | dirty_white = #eeeeee 14 | almost_dirty_white = #e6e6e6 15 | dirtier_white = #DAC6AF 16 | lighter_gray = #cccccc 17 | gray_a = #aaaaaa 18 | gray_9 = #999999 19 | light_gray = #888888 20 | gray_7 = #777777 21 | gray = #666666 22 | dark_gray = #444444 23 | gray_2 = #222222 24 | black = #111111 25 | light_color = #EDE4D8 26 | light_medium_color = #DDEAF0 27 | medium_color = #8ca1af 28 | medium_color_link = #634320 29 | medium_color_link_hover = #261a0c 30 | dark_color = rgba(160, 109, 52, 1.0) 31 | 32 | h1 = #1f3744 33 | h2 = #335C72 34 | h3 = #638fa6 35 | 36 | link_color = #335C72 37 | link_color_decoration = #99AEB9 38 | 39 | medium_color_hover = rgba(255, 255, 255, 0.25) 40 | medium_color = rgba(255, 255, 255, 0.5) 41 | green_highlight = #8ecc4c 42 | 43 | 44 | positive_dark = rgba(51, 77, 0, 1.0) 45 | positive_medium = rgba(102, 153, 0, 1.0) 46 | positive_light = rgba(102, 153, 0, 0.1) 47 | 48 | negative_dark = rgba(51, 13, 0, 1.0) 49 | negative_medium = rgba(204, 51, 0, 1.0) 50 | negative_light = rgba(204, 51, 0, 0.1) 51 | negative_text = #c60f0f 52 | 53 | ruler = #abc 54 | 55 | viewcode_bg = #f4debf 56 | viewcode_border = #ac9 57 | 58 | highlight = #ffe080 59 | 60 | code_background = rgba(0, 0, 0, 0.075) 61 | 62 | background = rgba(135, 57, 34, 1.0) 63 | background_link = rgba(212, 195, 172, 1.0) 64 | background_link_half = rgba(212, 195, 172, 0.5) 65 | background_text = rgba(212, 195, 172, 1.0) 66 | background_text_link = rgba(171, 138, 93, 1.0) 67 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # rdflib-web documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Jul 28 15:24:07 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os, re 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | sys.path.append('..') 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | source_encoding = 'utf-8' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'rdflib-web' 45 | copyright = u'2013, RDFLib Team' 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | 52 | def find_version(filename): 53 | _version_re = re.compile(r'__version__ = "(.*)"') 54 | for line in open(filename): 55 | version_match = _version_re.match(line) 56 | if version_match: 57 | return version_match.group(1) 58 | 59 | release = find_version('../rdflib_web/__init__.py') 60 | version = re.sub("[0-9]+\\.[0-9]\\..*", "\1", release) 61 | 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | #language = None 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = [] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | # If true, keep warnings as "system message" paragraphs in the built documents. 98 | #keep_warnings = False 99 | 100 | 101 | # -- Options for HTML output --------------------------------------------------- 102 | 103 | # The theme to use for HTML and HTML Help pages. See the documentation for 104 | # a list of builtin themes. 105 | html_theme = 'armstrong' 106 | 107 | # Theme options are theme-specific and customize the look and feel of a theme 108 | # further. For a list of options available for each theme, see the 109 | # documentation. 110 | #html_theme_options = {} 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | html_theme_path = ["_themes",] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | #html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | #html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | #html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | #html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ['_static'] 135 | 136 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 137 | # using the given strftime format. 138 | #html_last_updated_fmt = '%b %d, %Y' 139 | 140 | # If true, SmartyPants will be used to convert quotes and dashes to 141 | # typographically correct entities. 142 | #html_use_smartypants = True 143 | 144 | # Custom sidebar templates, maps document names to template names. 145 | #html_sidebars = {} 146 | 147 | # Additional templates that should be rendered to pages, maps page names to 148 | # template names. 149 | #html_additional_pages = {} 150 | 151 | # If false, no module index is generated. 152 | #html_domain_indices = True 153 | 154 | # If false, no index is generated. 155 | #html_use_index = True 156 | 157 | # If true, the index is split into individual pages for each letter. 158 | #html_split_index = False 159 | 160 | # If true, links to the reST sources are added to the pages. 161 | #html_show_sourcelink = True 162 | 163 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 164 | #html_show_sphinx = True 165 | 166 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 167 | #html_show_copyright = True 168 | 169 | # If true, an OpenSearch description file will be output, and all pages will 170 | # contain a tag referring to it. The value of this option must be the 171 | # base URL from which the finished HTML is served. 172 | #html_use_opensearch = '' 173 | 174 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 175 | #html_file_suffix = None 176 | 177 | # Output file base name for HTML help builder. 178 | htmlhelp_basename = 'rdflib-webdoc' 179 | 180 | 181 | # -- Options for LaTeX output -------------------------------------------------- 182 | 183 | latex_elements = { 184 | # The paper size ('letterpaper' or 'a4paper'). 185 | #'papersize': 'letterpaper', 186 | 187 | # The font size ('10pt', '11pt' or '12pt'). 188 | #'pointsize': '10pt', 189 | 190 | # Additional stuff for the LaTeX preamble. 191 | #'preamble': '', 192 | } 193 | 194 | # Grouping the document tree into LaTeX files. List of tuples 195 | # (source start file, target name, title, author, documentclass [howto/manual]). 196 | latex_documents = [ 197 | ('index', 'rdflib-web.tex', u'rdflib-web Documentation', 198 | u'RDFLib Team', 'manual'), 199 | ] 200 | 201 | # The name of an image file (relative to this directory) to place at the top of 202 | # the title page. 203 | #latex_logo = None 204 | 205 | # For "manual" documents, if this is true, then toplevel headings are parts, 206 | # not chapters. 207 | #latex_use_parts = False 208 | 209 | # If true, show page references after internal links. 210 | #latex_show_pagerefs = False 211 | 212 | # If true, show URL addresses after external links. 213 | #latex_show_urls = False 214 | 215 | # Documents to append as an appendix to all manuals. 216 | #latex_appendices = [] 217 | 218 | # If false, no module index is generated. 219 | #latex_domain_indices = True 220 | 221 | 222 | # -- Options for manual page output -------------------------------------------- 223 | 224 | # One entry per manual page. List of tuples 225 | # (source start file, name, description, authors, manual section). 226 | man_pages = [ 227 | ('index', 'rdflib-web', u'rdflib-web Documentation', 228 | [u'RDFLib Team'], 1) 229 | ] 230 | 231 | # If true, show URL addresses after external links. 232 | #man_show_urls = False 233 | 234 | 235 | # -- Options for Texinfo output ------------------------------------------------ 236 | 237 | # Grouping the document tree into Texinfo files. List of tuples 238 | # (source start file, target name, title, author, 239 | # dir menu entry, description, category) 240 | texinfo_documents = [ 241 | ('index', 'rdflib-web', u'rdflib-web Documentation', 242 | u'RDFLib Team', 'rdflib-web', 'One line description of project.', 243 | 'Miscellaneous'), 244 | ] 245 | 246 | # Documents to append as an appendix to all manuals. 247 | #texinfo_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | #texinfo_domain_indices = True 251 | 252 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 253 | #texinfo_show_urls = 'footnote' 254 | 255 | # If true, do not generate a @detailmenu in the "Top" node's menu. 256 | #texinfo_no_detailmenu = False 257 | 258 | 259 | # Example configuration for intersphinx: refer to the Python standard library. 260 | intersphinx_mapping = {'http://docs.python.org/': None} 261 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. rdflib-web documentation master file, created by 2 | sphinx-quickstart on Sun Jul 28 15:24:07 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to rdflib-web's documentation! 7 | ====================================== 8 | 9 | :mod:`rdflib_web` Package 10 | ------------------------- 11 | 12 | .. automodule:: rdflib_web.__init__ 13 | :members: 14 | :undoc-members: 15 | :show-inheritance: 16 | 17 | :mod:`endpoint` Module 18 | ---------------------- 19 | 20 | .. automodule:: rdflib_web.endpoint 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | :mod:`lod` Module 26 | ----------------- 27 | 28 | .. automodule:: rdflib_web.lod 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | Utilities 34 | ========= 35 | 36 | :mod:`bookdb` Module 37 | -------------------- 38 | 39 | .. automodule:: rdflib_web.bookdb 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`caches` Module 45 | -------------------- 46 | 47 | .. automodule:: rdflib_web.caches 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | 53 | :mod:`htmlresults` Module 54 | ------------------------- 55 | 56 | .. automodule:: rdflib_web.htmlresults 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | 61 | 62 | :mod:`mimeutils` Module 63 | ----------------------- 64 | 65 | .. automodule:: rdflib_web.mimeutils 66 | :members: 67 | :undoc-members: 68 | :show-inheritance: 69 | 70 | 71 | Indices and tables 72 | ================== 73 | 74 | * :ref:`genindex` 75 | * :ref:`modindex` 76 | * :ref:`search` 77 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\rdflib-web.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\rdflib-web.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /rdflib_web/__init__.py: -------------------------------------------------------------------------------- 1 | """rdflib-web contains two `Flask Blueprints `_ for exposing a 2 | ``SPARQL Endpoint`` and a Linked Open Data application for machine- and 3 | human-readable views of resources. 4 | """ 5 | 6 | 7 | __version__ = "0.1" 8 | -------------------------------------------------------------------------------- /rdflib_web/bookdb.py: -------------------------------------------------------------------------------- 1 | """ 2 | A tiny test graph with Book meta-data 3 | """ 4 | 5 | import rdflib 6 | import StringIO 7 | 8 | bookrdf=""" 9 | @prefix rdf: . 10 | @prefix rdfs: . 11 | @prefix book: . 12 | @prefix dc: . 13 | @prefix vcard: . 14 | 15 | a book:Book ; 16 | dc:creator "J.K. Rowling"; 17 | dc:title "Harry Potter and the Philosopher\'s Stone" . 18 | 19 | a book:Book ; 20 | book:author ; 21 | dc:title "Harry Potter & the Chamber of Secrets" . 22 | 23 | a book:Book ; 24 | dc:creator ; 25 | dc:title "Harry Potter and the Prisoner Of Azkaban" . 26 | 27 | dc:title "Harry Potter and the Goblet of Fire" . 28 | 29 | a book:Book ; 30 | dc:creator "J.K. Rowling"; 31 | dc:title "Harry Potter and the Order of the Ph\xc3\xb6nix" . 32 | 33 | a book:Book ; 34 | dc:creator "J.K. Rowling"; 35 | dc:title "Harry Potter and the Half-Blood Prince" . 36 | 37 | a book:Book ; 38 | dc:creator "J.K. Rowling"; 39 | dc:title "Harry Potter and the Deathly Hallows" . 40 | 41 | a book:Book ; 42 | dc:creator "Moosy"; 43 | dc:title "Moose bite can b\xc3\xb6 very nasty."@se ; 44 | dc:title "Elgbitt kan v\xc3\xa6re veldig vondt."@no ; . 45 | 46 | a book:Person ; 47 | vcard:Family "Rowling"; 48 | vcard:Given "Joanna" . 49 | 50 | book:Work a rdfs:Class ; 51 | rdfs:label "Work" . 52 | 53 | book:Publication a rdfs:Class ; 54 | rdfs:label "Publication" . 55 | 56 | book:Book a rdfs:Class ; 57 | rdfs:subClassOf book:Work, book:Publication ; 58 | rdfs:label "Book" . 59 | 60 | dc:creator a rdf:Property ; 61 | rdfs:label "creator" ; 62 | rdfs:domain book:Book . 63 | 64 | book:author a rdf:Property ; 65 | rdfs:label "author" ; 66 | rdfs:domain book:Book ; 67 | rdfs:subPropertyOf dc:creator . 68 | 69 | 70 | """ 71 | 72 | bookdb=rdflib.Graph() 73 | bookdb.parse(data=bookrdf,format='n3') 74 | -------------------------------------------------------------------------------- /rdflib_web/caches.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | LRU and LFU Caching decorators taken from py3.2 4 | 5 | """ 6 | 7 | import collections 8 | import functools 9 | 10 | from heapq import nsmallest 11 | from operator import itemgetter 12 | 13 | 14 | # this is added to python in 3.2, presumably more efficient than this 15 | # from 16 | # http://code.activestate.com/recipes/498245-lru-and-lfu-cache-decorators/ 17 | 18 | def lru_cache(maxsize=100): 19 | '''Least-recently-used cache decorator. 20 | 21 | Arguments to the cached function must be hashable. 22 | Cache performance statistics stored in f.hits and f.misses. 23 | http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used 24 | 25 | ''' 26 | def decorating_function(user_function): 27 | cache = collections.OrderedDict() # order: least recent to most recent 28 | 29 | @functools.wraps(user_function) 30 | def wrapper(*args, **kwds): 31 | key = args 32 | if kwds: 33 | key += tuple(sorted(kwds.items())) 34 | try: 35 | result = cache.pop(key) 36 | wrapper.hits += 1 37 | except KeyError: 38 | result = user_function(*args, **kwds) 39 | wrapper.misses += 1 40 | if len(cache) >= maxsize: 41 | cache.popitem(0) # purge least recently used cache entry 42 | cache[key] = result # record recent use of this key 43 | return result 44 | wrapper.hits = wrapper.misses = 0 45 | return wrapper 46 | return decorating_function 47 | 48 | def lfu_cache(maxsize=100): 49 | '''Least-frequenty-used cache decorator. 50 | 51 | Arguments to the cached function must be hashable. 52 | Cache performance statistics stored in f.hits and f.misses. 53 | Clear the cache with f.clear(). 54 | http://en.wikipedia.org/wiki/Least_Frequently_Used 55 | 56 | ''' 57 | 58 | def decorating_function(user_function): 59 | cache = {} # mapping of args to results 60 | use_count = collections.defaultdict(int) 61 | # times each key has been accessed 62 | kwd_mark = object() # separate positional and keyword args 63 | 64 | @functools.wraps(user_function) 65 | def wrapper(*args, **kwds): 66 | key = args 67 | if kwds: 68 | key += (kwd_mark,) + tuple(sorted(kwds.items())) 69 | use_count[key] += 1 70 | 71 | # get cache entry or compute if not found 72 | try: 73 | result = cache[key] 74 | wrapper.hits += 1 75 | except KeyError: 76 | result = user_function(*args, **kwds) 77 | cache[key] = result 78 | wrapper.misses += 1 79 | 80 | # purge least frequently used cache entry 81 | if len(cache) > maxsize: 82 | for key, _ in nsmallest(maxsize // 10, 83 | use_count.iteritems(), 84 | key=itemgetter(1)): 85 | try: 86 | del cache[key], use_count[key] 87 | except: 88 | pass 89 | 90 | return result 91 | 92 | def clear(): 93 | cache.clear() 94 | use_count.clear() 95 | wrapper.hits = wrapper.misses = 0 96 | 97 | wrapper.hits = wrapper.misses = 0 98 | wrapper.clear = clear 99 | return wrapper 100 | return decorating_function 101 | -------------------------------------------------------------------------------- /rdflib_web/endpoint.py: -------------------------------------------------------------------------------- 1 | """This is a Flask Blueprint for a ``SPARQL Endpoint`` confirming to the 2 | `SPARQL 1.0 Protocol `_. 3 | 4 | You can add the blueprint `endpoint` object to your own application:: 5 | 6 | from rdflib_web.endpoint import endpoint 7 | app = Flask(__name__) 8 | ... 9 | app.config['graph'] = my_rdflib_graph 10 | app.register_blueprint(endpoint) 11 | 12 | Or the application can be started from commandline:: 13 | 14 | python -m rdflib_web.endpoint 15 | 16 | or if you installed with pip, commands ``rdflodapp`` has been added to your path:: 17 | 18 | rdflodapp 19 | 20 | Either way, the endpoint will be available at http://localhost:5000 21 | 22 | You can also start the server from your application by calling the :py:func:`serve` method 23 | or get the application object yourself by called :py:func:`get` function 24 | 25 | """ 26 | try: 27 | from flask import ( Blueprint, Flask, render_template, request, make_response, 28 | Markup, g, url_for, current_app ) 29 | except: 30 | raise Exception("Flask not found - install with 'easy_install flask'") 31 | 32 | import rdflib 33 | 34 | import sys 35 | import time 36 | import traceback 37 | 38 | import mimeutils 39 | 40 | from rdflib_web import htmlresults 41 | from rdflib_web import __version__ 42 | from rdflib_web import generic_endpoint 43 | __all__ = [ 'endpoint', 'get', 'serve' ] 44 | 45 | 46 | endpoint = Blueprint('sparql_endpoint', __name__) 47 | """The Flask Blueprint object for a SPARQL endpoint on top of ``app.config["graph"]``""" 48 | 49 | @endpoint.record 50 | def setup(state): 51 | """Do a bit of application configuration""" 52 | 53 | state.app.jinja_env.globals["rdflib_version"]=rdflib.__version__ 54 | state.app.jinja_env.globals["rdflib_web_version"]=__version__ 55 | state.app.jinja_env.globals["python_version"]="%d.%d.%d"%(sys.version_info[0], sys.version_info[1], sys.version_info[2]) 56 | state.app.before_first_request(__create_generic_endpoint) 57 | state.app.before_first_request(__register_namespaces) 58 | 59 | 60 | DEFAULT = generic_endpoint.GenericEndpoint.DEFAULT 61 | 62 | @endpoint.route("/sparql", methods=['GET', 'POST']) 63 | def query(): 64 | try: 65 | q=request.values["query"] 66 | 67 | a=request.headers["Accept"] 68 | 69 | format="xml" # xml is default 70 | if mimeutils.HTML_MIME in a: 71 | format="html" 72 | if mimeutils.JSON_MIME in a: 73 | format="json" 74 | 75 | # output parameter overrides header 76 | format=request.values.get("output", format) 77 | 78 | mimetype=mimeutils.resultformat_to_mime(format) 79 | 80 | # force-accept parameter overrides mimetype 81 | mimetype=request.values.get("force-accept", mimetype) 82 | 83 | # pretty=None 84 | # if "force-accept" in request.values: 85 | # pretty=True 86 | 87 | # default-graph-uri 88 | 89 | results=g.generic.ds.query(q).serialize(format=format) 90 | if format=='html': 91 | response=make_response(render_template("results.html", results=Markup(unicode(results,"utf-8")), q=q)) 92 | else: 93 | response=make_response(results) 94 | 95 | response.headers["Content-Type"]=mimetype 96 | return response 97 | except: 98 | return "
"+traceback.format_exc()+"
", 400 99 | 100 | def graph_store_do(graph_identifier): 101 | method = request.method 102 | mimetype = request.mimetype 103 | args = request.args 104 | if mimetype == "multipart/form-data": 105 | body = [] 106 | force_mimetype = args.get('mimetype') 107 | for _, data_file in request.files.items(): 108 | data = data_file.read() 109 | mt = force_mimetype or data_file.mimetype or rdflib.guess_format(data_file.filename) 110 | body.append({'data': data, 'mimetype': mt}) 111 | else: 112 | body = request.data 113 | 114 | result = g.generic.graph_store( 115 | method=method, graph_identifier=graph_identifier, args=args, 116 | body=body, mimetype=mimetype, 117 | accept_header=request.headers.get("Accept") 118 | ) 119 | code, headers, body = result 120 | 121 | response = make_response(body or '', code) 122 | for k, v in headers.items(): 123 | response.headers[k] = v 124 | return response 125 | 126 | 127 | @endpoint.route("/graph-store", methods=["GET", "PUT", "POST", "DELETE"]) # HEAD is done by flask via GET 128 | def graph_store_indirect(): 129 | return graph_store_do(None) 130 | 131 | @endpoint.route("/graph-store/", methods=["GET", "POST", "PUT", "DELETE"]) # HEAD is done by flask via GET 132 | def graph_store_direct(path): 133 | graph_identifier = rdflib.URIRef(request.url) 134 | return graph_store_do(graph_identifier) 135 | 136 | 137 | #@endpoint.route("/") # bound later 138 | def index(): 139 | return render_template("index.html") 140 | 141 | def __register_namespaces(): 142 | for p,ns in current_app.config["graph"].namespaces(): 143 | htmlresults.nm.bind(p,ns,override=True) 144 | 145 | def __create_generic_endpoint(): 146 | current_app.config["generic"]=generic_endpoint.GenericEndpoint( 147 | ds=current_app.config["graph"], 148 | coin_url=lambda: url_for("graph_store_direct", path=str(rdflib.BNode()), _external=True) 149 | ) 150 | 151 | @endpoint.before_request 152 | def __start(): 153 | g.start=time.time() 154 | 155 | @endpoint.after_request 156 | def __end(response): 157 | diff = time.time() - g.start 158 | if response.response and response.content_type.startswith("text/html") and response.status_code==200: 159 | response.response[0]=response.response[0].replace('__EXECUTION_TIME__', "%.3f"%diff) 160 | response.headers["Content-Length"]=len(response.response[0]) 161 | return response 162 | 163 | 164 | def serve(ds,debug=False): 165 | """Serve the given dataset on localhost with the LOD App""" 166 | 167 | a=get(ds) 168 | a.run(debug=debug) 169 | return a 170 | 171 | @endpoint.before_app_request 172 | def _set_generic(): 173 | """ This sets the g.generic if we are using a static graph 174 | set in the configuration""" 175 | g.generic = current_app.config["generic"] 176 | g.graph = g.generic.ds 177 | 178 | def get(ds): 179 | """ 180 | Get the LOD Flask App setup to serve the given dataset 181 | """ 182 | app = Flask(__name__) 183 | app.config["graph"]=ds 184 | 185 | app.register_blueprint(endpoint) 186 | app.add_url_rule('/', 'index', index) 187 | 188 | return app 189 | 190 | 191 | def _main(g, out, opts): 192 | import rdflib 193 | import sys 194 | 195 | if 'x' in opts: 196 | import bookdb 197 | g=bookdb.bookdb 198 | 199 | serve(g, True) 200 | 201 | def main(): 202 | from rdflib.extras.cmdlineutils import main as cmdmain 203 | cmdmain(_main, stdin=False, options='x') 204 | 205 | if __name__=='__main__': 206 | main() 207 | -------------------------------------------------------------------------------- /rdflib_web/generic_endpoint.py: -------------------------------------------------------------------------------- 1 | import rdflib 2 | 3 | class DefaultGraphReadOnly(Exception): 4 | pass 5 | 6 | class NamedGraphsNotSupported(Exception): 7 | pass 8 | 9 | class GenericEndpoint: 10 | """ 11 | This is an implementation of the SPAQL 1.1 Protocol and SPARQL 1.1 12 | Graph Store Protocol suitable for integration into webserver 13 | frameworks. 14 | """ 15 | 16 | def __init__(self, ds, coin_url): 17 | """ 18 | :argument:ds: The dataset to be used. Must be a Dataset (recommeded), 19 | ConjunctiveGraph or Graph. In case of a Graph, it is served as 20 | the default graph. 21 | :argument:coin_url: A function that takes no arguments and outputs 22 | an URI for a fresh graph. This is used when graph_identifier is 23 | None and neither 'default' nor 'graph' can be found in args. 24 | """ 25 | self.ds = ds 26 | self.coin_url = coin_url 27 | 28 | DEFAULT = 'DEFAULT' 29 | 30 | RESULT_GRAPH = 0 31 | 32 | def negotiate(self, resulttype, accept_header): 33 | #TODO: Find all mimetypes supported by the serializers 34 | #automatically instead of hardcoded 35 | import logging 36 | if resulttype == self.RESULT_GRAPH: 37 | available = ['application/n-triples', 'text/n3', 'text/turtle', 'application/rdf+xml'] 38 | assert available, "Invalid resulttype" 39 | import mimeutils 40 | best = mimeutils.best_match(available, accept_header) or available[-1] 41 | return best, best 42 | 43 | def graph_store(self, method, graph_identifier, args, body, mimetype, accept_header): 44 | """Handles a request according to the SPARQL 1.1 graph store 45 | protocol. 46 | 47 | :argument:method: 'PUT', 'POST', 'DELETE', or 'GET' 48 | :argument:graph_identifier: rdflib.URIRef of the graph against 49 | which the request is made. It must be None for indirect requests. The 50 | special value GenericEndpoint.DEFAULT denotes the default graph. 51 | :argument:args: A dict containing all URL parameters 52 | :argument:body: The request body as list of dicts if the 53 | content-type is multipart/form-data, otherwise a string. 54 | :argument:mimetype: The mime type part (i.e. without charset) of 55 | the request body 56 | :argument:accept_header: The accept header value as given by 57 | the client. This is required for content negotiation. 58 | 59 | :Returns: 60 | 61 | A triple consisting of the HTTP status code, a dictionary of 62 | headers that must be included in the status, and the body of 63 | the status. In case of an error (i.e. the status code is 64 | at least 400), then the body only consists of a error message 65 | as string. In this case, the caller is responsible to create a 66 | proper status body. If the status code is 201 or 204, the body 67 | is None. 68 | 69 | This method can through exceptions. If this happens, it is always an 70 | internal error. 71 | """ 72 | if not graph_identifier: 73 | if 'default' in args: 74 | graph_identifier = self.DEFAULT 75 | elif 'graph' in args: 76 | graph_identifier = rdflib.URIRef(args['graph']) 77 | elif method == 'POST': 78 | graph_identifier = None 79 | else: 80 | return (400, dict(), "Missing URL query string parameter 'graph' or 'default'") 81 | 82 | existed = False 83 | if graph_identifier == self.DEFAULT: 84 | existed = True 85 | elif graph_identifier and self.ds.context_aware: 86 | existed = graph_identifier in {g.identifier for g in self.ds.contexts()} 87 | 88 | def get_graph(identifier): 89 | # Creates the graph if it does not already exist and returns 90 | # it. 91 | if graph_identifier == self.DEFAULT: 92 | # A ConjunctiveGraph or Datset itself represents the 93 | # default graph (it might be the union of all graphs). 94 | # In case of a plain Graph, the default graph is the 95 | # graph itself too. 96 | return self.ds 97 | elif hasattr(self.ds, "graph"): # No Graph.graph_aware 98 | return self.ds.graph(identifier) 99 | elif self.ds.context_aware: 100 | return self.ds.get_context(identifier) 101 | else: 102 | raise NamedGraphsNotSupported() 103 | 104 | def clear_graph(identifier): 105 | if identifier == self.DEFAULT: 106 | if self.ds.default_union: 107 | raise DefaultGraphReadOnly() 108 | elif self.ds.context_aware: 109 | self.ds.default_context.remove((None,None,None)) 110 | else: 111 | self.ds.remove((None,None,None)) 112 | else: 113 | self.ds.remove((None, None, None, get_graph(identifier))) 114 | 115 | def remove_graph(identifier): 116 | # Unfortunately, there is no Graph.graph_aware, so use 117 | # hasattr 118 | if identifier == self.DEFAULT and self.ds.default_union: 119 | raise DefaultGraphReadOnly() 120 | elif hasattr(self.ds, "remove_graph"): 121 | self.ds.remove_graph(get_graph(identifier)) 122 | else: 123 | clear_graph(identifier) 124 | 125 | def parseInto(target, data, format): 126 | # Makes shure that the for ConjucntiveGraph and Dataset we 127 | # parse into the default graph instead of into a fresh 128 | # graph. 129 | if target.default_union: 130 | raise DefaultGraphReadOnly() 131 | if target.context_aware: 132 | target.default_context.parse(data=data, format=format) 133 | else: 134 | target.parse(data=data, format=format) 135 | 136 | try: 137 | 138 | if method == 'PUT': 139 | if existed: 140 | clear_graph(graph_identifier) 141 | target = get_graph(graph_identifier) 142 | parseInto(target, data=body, format=mimetype) 143 | response = (204 if existed else 201, dict(), None) 144 | 145 | elif method == 'DELETE': 146 | if existed: 147 | remove_graph(graph_identifier) 148 | response = (204, dict(), None) 149 | else: 150 | response = (404, dict(), 'Graph %s not found' % graph_identifier) 151 | 152 | elif method == 'POST': 153 | additional_headers = dict() 154 | if not graph_identifier: 155 | # Coin a new identifier 156 | existed = False 157 | url = self.coin_url() 158 | graph_identifier = rdflib.URIRef(url) 159 | additional_headers['location'] = url 160 | target = get_graph(graph_identifier) 161 | if mimetype == "multipart/form-data": 162 | for post_item in body: 163 | target = get_graph(graph_identifier) 164 | parseInto(target, data=post_item['data'], format=post_item['mimetype']) 165 | else: 166 | parseInto(target, data=body, format=mimetype) 167 | response = (204 if existed else 201, additional_headers, None) 168 | 169 | elif method == 'GET' or method == 'HEAD': 170 | if existed: 171 | format, content_type = self.negotiate(self.RESULT_GRAPH, accept_header) 172 | if content_type.startswith('text/'): content_type += "; charset=utf-8" 173 | headers = {"Content-type": content_type} 174 | response = (200, headers, get_graph(graph_identifier).serialize(format=format)) 175 | else: 176 | response = (404, dict(), 'Graph %s not found' % graph_identifier) 177 | 178 | else: 179 | response = (405, {"Allow": "GET, HEAD, POST, PUT, DELETE"}, "Method %s not supported" % method) 180 | 181 | except DefaultGraphReadOnly: 182 | response = (400, dict(), "Default graph is read only because it is the uion") 183 | except NamedGraphsNotSupported: 184 | response = (400, dict(), "Named graphs not supported") 185 | 186 | return response 187 | 188 | -------------------------------------------------------------------------------- /rdflib_web/htmlresults.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | SPARQL Result Serializer and RDF Serializer 4 | for nice human readable HTML tables 5 | 6 | >>> from rdflib import RDF, RDFS, XSD, Graph, Literal 7 | >>> g=Graph() 8 | >>> g.add((RDF.Property, RDF.type, RDFS.Class)) 9 | >>> g.add((RDF.Property, RDFS.label, Literal('Property'))) 10 | >>> g.add((RDF.Property, RDFS.label, Literal('Property', datatype=XSD.string))) 11 | >>> g.add((RDF.Property, RDFS.label, Literal('Eigenschaft', lang='de'))) 12 | 13 | >>> s=g.query('select * where { ?s ?p ?o . }').serialize(format='html') 14 | >>> 'rdf:type' in s 15 | True 16 | >>> '@de' in s 17 | True 18 | >>> '^^<xsd:string>' in s 19 | True 20 | """ 21 | 22 | import rdflib 23 | from rdflib.query import ResultSerializer 24 | from rdflib.serializer import Serializer 25 | 26 | import warnings 27 | 28 | from jinja2 import Environment, contextfilter, Markup 29 | 30 | nm=rdflib.Graph().namespace_manager 31 | nm.bind('xsd',rdflib.XSD) 32 | 33 | def qname(ctx, t): 34 | try: 35 | if "graph" in ctx: 36 | l=ctx["graph"].namespace_manager.compute_qname(t, False) 37 | else: 38 | l=nm.compute_qname(t, False) 39 | return u'%s:%s'%(l[0],l[2]) 40 | except: 41 | return t 42 | 43 | 44 | @contextfilter 45 | def term_to_string(ctx, t): 46 | if isinstance(t, rdflib.URIRef): 47 | l=qname(ctx,t) 48 | return Markup(u"%s"%(t,l)) 49 | elif isinstance(t, rdflib.Literal): 50 | if t.language: 51 | return '"%s"@%s'%(t,t.language) 52 | elif t.datatype: 53 | return '"%s"^^<%s>'%(t,qname(ctx,t.datatype)) 54 | else: 55 | return '"%s"'%t 56 | return t 57 | 58 | env=Environment() 59 | env.filters["term_to_string"]=term_to_string 60 | 61 | 62 | GRAPH_TEMPLATE=u""" 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {% for t in graph %} 73 | 74 | {% for x in t %} 75 | 76 | {% endfor %} 77 | 78 | {% endfor %} 79 | 80 |
subjectpredicateobject
{{x|term_to_string}}
81 | 82 | """ 83 | 84 | SELECT_TEMPLATE=u""" 85 | 86 | 87 | 88 | {% for var in result.vars %} 89 | 90 | {% endfor %} 91 | 92 | 93 | 94 | {% for row in result.bindings %} 95 | 96 | {% for var in result.vars %} 97 | 98 | {% endfor %} 99 | 100 | {% endfor %} 101 | 102 |
{{var}}
{{row[var]|term_to_string}}
103 | 104 | """ 105 | 106 | 107 | class HTMLResultSerializer(ResultSerializer): 108 | 109 | def __init__(self, result): 110 | ResultSerializer.__init__(self, result) 111 | 112 | def serialize(self, stream, encoding="utf-8"): 113 | if self.result.type=='ASK': 114 | stream.write("true".encode(encoding)) 115 | return 116 | if self.result.type=='SELECT': 117 | template = env.from_string(SELECT_TEMPLATE) 118 | stream.write(template.render(result=self.result)) 119 | 120 | 121 | 122 | 123 | 124 | 125 | class HTMLSerializer(Serializer): 126 | """ 127 | Serializes RDF graphs as HTML tables 128 | """ 129 | 130 | def serialize(self, stream, base=None, encoding=None, **args): 131 | if base is not None: 132 | warnings.warn("HTMLSerializer does not support base.") 133 | if encoding is not None: 134 | warnings.warn("HTMLSerializer does not use custom encoding.") 135 | 136 | template = env.from_string(GRAPH_TEMPLATE) 137 | res=template.render(graph=self.store) 138 | if not encoding: encoding="utf-8" 139 | 140 | res=res.encode(encoding) 141 | stream.write(res) 142 | 143 | 144 | if __name__=='__main__': 145 | import rdflib 146 | g=rdflib.Graph() 147 | g.add((rdflib.RDF.Property, rdflib.RDF.type, rdflib.RDFS.Class)) 148 | g.add((rdflib.RDF.Property, rdflib.RDFS.label, rdflib.Literal('Property'))) 149 | g.add((rdflib.RDF.Property, rdflib.RDFS.label, rdflib.Literal('Property', datatype=rdflib.XSD.string))) 150 | g.add((rdflib.RDF.Property, rdflib.RDFS.label, rdflib.Literal('Eigenschaft', lang='de'))) 151 | 152 | s=g.query('select * where { ?s ?p ?o . }').serialize(format='html') 153 | 154 | -------------------------------------------------------------------------------- /rdflib_web/lod.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a Flask web-app for a simple Linked Open Data Web-app 3 | it also includes a ``SPARQL 1.0 Endpoint``. 4 | 5 | You can add the blueprint `lod` object to your own application:: 6 | 7 | from rdflib_web.lod import lod 8 | app = Flask(__name__) 9 | ... 10 | app.config['graph'] = my_rdflib_graph 11 | app.register_blueprint(lod) 12 | 13 | Or the application can be started from commandline:: 14 | 15 | python -m rdflib_web.lod 16 | 17 | or if you installed with pip, commands ``rdflodapp`` has been added to your path:: 18 | 19 | rdflodapp 20 | 21 | Either way, the file will be served from http://localhost:5000 22 | 23 | You can also start the server from your application by calling the :py:func:`serve` method 24 | or get the application object yourself by called :py:func:`get` function 25 | 26 | The application creates local URIs based on the type of resources 27 | and servers content-negotiated HTML or serialised RDF from these. 28 | 29 | """ 30 | import re 31 | import warnings 32 | import urllib2 33 | import collections 34 | import subprocess 35 | import codecs 36 | import os.path 37 | import itertools 38 | 39 | import rdflib 40 | 41 | from rdflib import RDF, RDFS 42 | import rdflib.util as util 43 | from rdflib.tools import rdfs2dot, rdf2dot 44 | 45 | 46 | from flask import Flask, Blueprint, render_template, request, \ 47 | make_response, redirect, url_for, g, Response, session, current_app 48 | 49 | from werkzeug.routing import BaseConverter 50 | from werkzeug.urls import url_quote 51 | 52 | from jinja2 import contextfilter, Markup 53 | 54 | from rdflib_web.endpoint import endpoint 55 | from rdflib_web import mimeutils 56 | 57 | from rdflib_web.caches import lfu_cache 58 | 59 | __all__ = ['lod', 'get', 'serve' ] 60 | 61 | lod = Blueprint('lod', __name__) 62 | """The Flask Blueprint object for a LOD Application on top of 63 | ``app.config["graph"]``. See :py:func:`get` for further configuration options.""" 64 | 65 | POSSIBLE_DOT=["/usr/bin/dot", "/usr/local/bin/dot", "/opt/local/bin/dot"] 66 | for x in POSSIBLE_DOT: 67 | if os.path.exists(x): 68 | DOT=x 69 | break 70 | 71 | GRAPH_TYPES={"png": "image/png", 72 | "svg": "image/svg+xml", 73 | "dot": "text/x-graphviz", 74 | "pdf": "application/pdf" } 75 | 76 | 77 | class RDFUrlConverter(BaseConverter): 78 | def __init__(self, url_map): 79 | BaseConverter.__init__(self,url_map) 80 | self.regex="[^/].*?" 81 | def to_url(self, value): 82 | return url_quote(value, self.map.charset, safe=":") 83 | 84 | @contextfilter 85 | def termdict_link(ctx, t): 86 | if not t: return "" 87 | if isinstance(t,dict): 88 | cls='class="external"' if t['external'] else '' 89 | 90 | return Markup("%s"%(cls, t['url'], t['label'])) 91 | else: 92 | return [termdict_link(ctx,x) for x in t] 93 | 94 | 95 | def is_rdf_node(t): 96 | return isinstance(t, rdflib.term.Node) 97 | 98 | @lod.record 99 | def setup_lod(state): 100 | app = state.app 101 | 102 | app.url_map.converters['rdf'] = RDFUrlConverter 103 | app.jinja_env.filters["term"]=termdict_link 104 | app.jinja_env.tests["rdf_node"]=is_rdf_node 105 | 106 | graph = app.config['graph'] 107 | 108 | types = app.config.get('types', 'auto') 109 | 110 | foundtypes, resource_types=_find_types(graph) 111 | if types=='auto': 112 | app.config["types"]=foundtypes 113 | elif types==None: 114 | app.config["types"]={None:None} 115 | else: 116 | app.config["types"]=types 117 | 118 | app.config["resource_types"]=resource_types 119 | 120 | app.config["rtypes"]=_reverse_types(app.config["types"]) 121 | 122 | app.config["resources"]=_find_resources(graph, app.config['types']) 123 | app.config["rresources"]=_reverse_resources(app.config["resources"]) 124 | 125 | app.config["labels"]=_find_labels(graph, app.config["resources"], 126 | app.config.get('label_properties', LABEL_PROPERTIES)) 127 | 128 | 129 | LABEL_PROPERTIES=[RDFS.label, 130 | rdflib.URIRef("http://purl.org/dc/elements/1.1/title"), 131 | rdflib.URIRef("http://xmlns.com/foaf/0.1/name"), 132 | rdflib.URIRef("http://www.w3.org/2006/vcard/ns#fn"), 133 | rdflib.URIRef("http://www.w3.org/2006/vcard/ns#org") 134 | 135 | ] 136 | @lfu_cache(200) 137 | def resolve(r): 138 | """ 139 | URL is the potentially local URL 140 | realurl is the one used in the data. 141 | 142 | return {url, realurl, label} 143 | """ 144 | 145 | if r==None: 146 | return { 'url': None, 'realurl': None, 'label': None } 147 | if isinstance(r, rdflib.Literal): 148 | return { 'url': None, 'realurl': None, 'label': unicode(r), 'lang': r.language } 149 | 150 | # if str(r)=='http://www.agroxml.de/rdf/vocabulary/workProcess#WorkProcess': 151 | # asldkj 152 | 153 | t=None 154 | localurl=None 155 | if current_app.config["types"]=={None: None}: 156 | if current_app.config["resource_types"][r] in g.graph: 157 | localurl=url_for("lod.resource", label=current_app.config["resources"][None][r]) 158 | else: 159 | for t in current_app.config["resource_types"][r]: 160 | if t in current_app.config["types"]: 161 | try: 162 | l=current_app.config["resources"][t][r].decode("utf8") 163 | localurl=url_for("lod.resource", type_=current_app.config["types"][t], label=l) 164 | break 165 | except KeyError: pass 166 | 167 | types=[ resolve(t) for t in current_app.config["resource_types"][r] if t!=r] 168 | 169 | return { 'external': not localurl, 170 | 'url': localurl or r, 171 | 'realurl': r, 172 | 'label': get_label(r), 173 | 'type': types, 174 | 'picked': unicode(r) in session["picked"]} 175 | 176 | def localname(t): 177 | """standard rdflib qname computer is not quite what we want""" 178 | 179 | r=t[max(t.rfind("/"), t.rfind("#"))+1:] 180 | # pending apache 2.2.18 being available 181 | # work around %2F encoding bug for AllowEncodedSlashes apache option 182 | r=r.replace("%2F", "_") 183 | return r 184 | 185 | def _find_label(t, graph, label_props): 186 | if isinstance(t, rdflib.Literal): return unicode(t) 187 | for l in label_props: 188 | try: 189 | return graph.objects(t,l).next() 190 | except StopIteration: 191 | pass 192 | try: 193 | #return g.graph.namespace_manager.compute_qname(t)[2] 194 | return urllib2.unquote(localname(t)) 195 | except: 196 | return t 197 | 198 | 199 | def get_label(r): 200 | try: 201 | return current_app.config["labels"][r] 202 | except: 203 | try: 204 | l=urllib2.unquote(localname(r)) 205 | except: 206 | l=r 207 | current_app.config["labels"][r]=l 208 | return l 209 | 210 | 211 | def label_to_url(label): 212 | label=re.sub(re.compile('[^\w ]',re.U), '',label) 213 | return re.sub(" ", "_", label) 214 | 215 | def _find_types(graph): 216 | types={} 217 | resource_types=collections.defaultdict(set) 218 | types[RDFS.Class]=localname(RDFS.Class) 219 | types[RDF.Property]=localname(RDF.Property) 220 | for s,p,o in graph.triples((None, RDF.type, None)): 221 | if o not in types: types[o]=_quote(localname(o)) 222 | resource_types[s].add(o) 223 | 224 | for t in types: 225 | resource_types[t].add(RDFS.Class) 226 | 227 | return types, resource_types 228 | 229 | def _reverse_types(types): 230 | """Generate cache of localname=>type mapping""" 231 | rtypes={} 232 | for t,l in types.iteritems(): 233 | while l in rtypes: 234 | warnings.warn(u"Multiple types for label '%s': (%s) rewriting to '%s_'"%(l,rtypes[l], l)) 235 | l+="_" 236 | rtypes[l]=t 237 | 238 | # rewrite type cache, in case we changed some labels 239 | types.clear() 240 | for l,t in rtypes.iteritems(): 241 | types[t]=l 242 | return rtypes 243 | 244 | 245 | def _find_resources(graph, types): 246 | 247 | """Build up cache of type=>[resource=>localname]]""" 248 | 249 | resources=collections.defaultdict(dict) 250 | 251 | for t in types: 252 | resources[t]={} 253 | for x in graph.subjects(RDF.type, t): 254 | resources[t][x]=_quote(localname(x)) 255 | 256 | resources[RDFS.Class].update(types.copy()) 257 | 258 | return resources 259 | 260 | def _reverse_resources(resources): 261 | """ 262 | Reverse resource-cache, build up cache 263 | type=>[localname=>resource] 264 | 265 | (for finding resources when entering URL) 266 | """ 267 | rresources={} 268 | for t,res in resources.iteritems(): 269 | rresources[t]={} 270 | for r, l in res.iteritems(): 271 | while l in rresources[t]: 272 | warnings.warn(u"Multiple resources for label '%s': (%s, %s) rewriting to '%s_'"%(repr(l),rresources[t][l], r, repr(l+'_'))) 273 | l+="_" 274 | 275 | rresources[t][l]=r 276 | 277 | resources[t].clear() 278 | for l,r in rresources[t].iteritems(): 279 | resources[t][r]=l 280 | 281 | return rresources 282 | 283 | def _find_labels(graph, resources, label_props): 284 | labels={} 285 | for t, res in resources.iteritems(): 286 | for r in res: 287 | if r not in labels: 288 | labels[r]=_find_label(r, graph, label_props) 289 | return labels 290 | 291 | def _quote(l): 292 | if isinstance(l,unicode): 293 | l=l.encode("utf-8") 294 | return l 295 | #return urllib2.quote(l, safe="") 296 | 297 | 298 | def get_resource(label, type_): 299 | label=_quote(label) 300 | if type_ and type_ not in current_app.config["rtypes"]: 301 | return "No such type_ %s"%type_, 404 302 | try: 303 | t=current_app.config["rtypes"][type_] 304 | return current_app.config["rresources"][t][label] 305 | 306 | except: 307 | return "No such resource %s"%label, 404 308 | 309 | @lod.route("/download/") 310 | def download(format_): 311 | return serialize(g.graph, format_) 312 | 313 | @lod.route("/rdfgraph//.") 314 | @lod.route("/rdfgraph/.") 315 | def rdfgraph(label, format_,type_=None): 316 | r=get_resource(label, type_) 317 | if isinstance(r,tuple): # 404 318 | return r 319 | 320 | graph=_resourceGraph(r) 321 | 322 | return graphrdf(graph, format_) 323 | 324 | def graphrdf(graph, format_): 325 | return dot(lambda uw: rdf2dot.rdf2dot(graph, stream=uw), format_) 326 | 327 | def graphrdfs(graph, format_): 328 | return dot(lambda uw: rdfs2dot.rdfs2dot(graph, stream=uw), format_) 329 | 330 | def dot(inputgraph, format_): 331 | 332 | if format_ not in GRAPH_TYPES: 333 | return "format '%s' not supported, try %s"%(format_, ", ".join(GRAPH_TYPES)), 415 334 | 335 | rankdir=request.args.get("rankdir", "BT") 336 | p=subprocess.Popen([DOT, "-Grankdir=%s"%rankdir, "-T%s"%format_], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 337 | uw=codecs.getwriter("utf8")(p.stdin) 338 | inputgraph(uw) 339 | 340 | p.stdin.close() 341 | def readres(): 342 | s=p.stdout.read(1000) 343 | while s!="": 344 | yield s 345 | s=p.stdout.read(1000) 346 | 347 | return Response(readres(), mimetype=GRAPH_TYPES[format_]) 348 | 349 | def _addTypesLabels(subgraph, graph): 350 | addMe=[] 351 | for o in itertools.chain(subgraph.objects(None,None),subgraph.subjects(None,None)): 352 | if not isinstance(o, rdflib.term.Node): continue 353 | addMe+=list(graph.triples((o,RDF.type, None))) 354 | for l in current_app.config["label_properties"]: 355 | if (o, l, None) in graph: 356 | addMe+=list(graph.triples((o, l, None))) 357 | break 358 | subgraph+=addMe 359 | 360 | def _resourceGraph(r): 361 | graph=rdflib.Graph() 362 | for p,ns in g.graph.namespaces(): 363 | graph.bind(p,ns) 364 | 365 | graph+=g.graph.triples((r,None,None)) 366 | graph+=g.graph.triples((None,None,r)) 367 | 368 | if not "notypes" in request.args and current_app.config["add_types_labels"]: 369 | _addTypesLabels(graph, g.graph) 370 | 371 | return graph 372 | 373 | 374 | @lod.route("/data//.") 375 | @lod.route("/data/.") 376 | def data(label, format_, type_=None): 377 | r=get_resource(label, type_) 378 | if isinstance(r,tuple): # 404 379 | return r 380 | 381 | graph=_resourceGraph(r) 382 | #graph=g.graph.query('DESCRIBE %s'%r.n3()) 383 | # DESCRIBE is broken. 384 | # http://code.google.com/p/rdfextras/issues/detail?id=25 385 | 386 | return serialize(graph, format_) 387 | 388 | def serialize(graph, format_): 389 | 390 | format_,mimetype_=mimeutils.format_to_mime(format_) 391 | 392 | response=make_response(graph.serialize(format=format_)) 393 | 394 | response.headers["Content-Type"]=mimetype_ 395 | 396 | return response 397 | 398 | 399 | @lod.route("/page//") 400 | @lod.route("/page/") 401 | def page(label, type_=None): 402 | r=get_resource(label, type_) 403 | if isinstance(r,tuple): # 404 404 | return r 405 | 406 | special_props=(RDF.type, RDFS.comment, RDFS.label, 407 | RDFS.domain, RDFS.range, 408 | RDFS.subClassOf, RDFS.subPropertyOf) 409 | 410 | outprops=sorted([ (resolve(x[0]), resolve(x[1])) for x in g.graph.predicate_objects(r) if x[0] not in special_props]) 411 | 412 | types=sorted([ resolve(x) for x in current_app.config["resource_types"][r]], key=lambda x: x['label'].lower()) 413 | 414 | comments=list(g.graph.objects(r,RDFS.comment)) 415 | 416 | inprops=sorted([ (resolve(x[0]), resolve(x[1])) for x in g.graph.subject_predicates(r) ]) 417 | 418 | picked=unicode(r) in session["picked"] 419 | 420 | params={ "outprops":outprops, 421 | "inprops":inprops, 422 | "label":get_label(r), 423 | "urilabel":label, 424 | "comments":comments, 425 | "graph":g.graph, 426 | "type_":type_, 427 | "types":types, 428 | "resource":r, 429 | "picked":picked } 430 | p="lodpage.html" 431 | 432 | if r==RDF.Property: 433 | # page for all properties 434 | roots=util.find_roots(g.graph, RDFS.subPropertyOf, set(current_app.config["resources"][r])) 435 | roots=sorted(roots, key=lambda x: get_label(x).lower()) 436 | params["properties"]=[util.get_tree(g.graph, root, RDFS.subPropertyOf, resolve, lambda x: x[0]['label'].lower()) for root in roots] 437 | 438 | for x in inprops[:]: 439 | if x[1]["url"]==RDF.type: 440 | inprops.remove(x) 441 | 442 | 443 | p="properties.html" 444 | elif RDF.Property in current_app.config["resource_types"][r]: 445 | # a single property 446 | 447 | params["properties"]=[util.get_tree(g.graph, r, RDFS.subPropertyOf, resolve)] 448 | 449 | superProp=[resolve(x) for x in g.graph.objects(r,RDFS.subPropertyOf) ] 450 | if superProp: 451 | params["properties"]=[(superProp, params["properties"])] 452 | 453 | params["domain"]=[resolve(do) for do in g.graph.objects(r,RDFS.domain)] 454 | params["range"]=[resolve(ra) for ra in g.graph.objects(r,RDFS.range)] 455 | 456 | # show subclasses/instances only once 457 | for x in inprops[:]: 458 | if x[1]["url"] in (RDFS.subPropertyOf, ): 459 | inprops.remove(x) 460 | 461 | p="property.html" 462 | elif r==RDFS.Class or r==rdflib.OWL.Class: 463 | # page for all classes 464 | roots=util.find_roots(g.graph, RDFS.subClassOf, set(current_app.config["types"])) 465 | roots=sorted(roots, key=lambda x: get_label(x).lower()) 466 | params["classes"]=[util.get_tree(g.graph, root, RDFS.subClassOf, resolve, sortkey=lambda x: x[0]['label'].lower()) for root in roots] 467 | 468 | p="classes.html" 469 | # show classes only once 470 | for x in inprops[:]: 471 | if x[1]["url"]==RDF.type: 472 | inprops.remove(x) 473 | 474 | elif RDFS.Class in current_app.config["resource_types"][r] or rdflib.OWL.Class in current_app.config["resource_types"][r]: 475 | # page for a single class 476 | 477 | params["classes"]=[util.get_tree(g.graph, r, RDFS.subClassOf, resolve)] 478 | 479 | superClass=[resolve(x) for x in g.graph.objects(r,RDFS.subClassOf) ] 480 | if superClass: 481 | params["classes"]=[(superClass, params["classes"])] 482 | 483 | params["classoutprops"]=[(resolve(p), [resolve(pr) for pr in g.graph.objects(p,RDFS.range)]) for p in g.graph.subjects(RDFS.domain,r)] 484 | params["classinprops"]=[([resolve(pr) for pr in g.graph.objects(p,RDFS.domain)],resolve(p)) for p in g.graph.subjects(RDFS.range,r)] 485 | 486 | params["instances"]=[] 487 | # show subclasses/instances only once 488 | for x in inprops[:]: 489 | if x[1]["url"]==RDF.type: 490 | inprops.remove(x) 491 | params["instances"].append(x[0]) 492 | elif x[1]["url"] in (RDFS.subClassOf, 493 | RDFS.domain, 494 | RDFS.range): 495 | inprops.remove(x) 496 | 497 | p="class.html" 498 | 499 | 500 | 501 | return render_template(p, **params) 502 | 503 | @lod.route("/resource//") 504 | @lod.route("/resource/") 505 | def resource(label, type_=None): 506 | """ 507 | Do ContentNegotiation for some resource and 508 | redirect to the appropriate place 509 | """ 510 | 511 | mimetype=mimeutils.best_match([mimeutils.RDFXML_MIME, mimeutils.N3_MIME, 512 | mimeutils.NTRIPLES_MIME, mimeutils.HTML_MIME], request.headers.get("Accept")) 513 | 514 | if mimetype and mimetype!=mimeutils.HTML_MIME: 515 | path="lod.data" 516 | ext="."+mimeutils.mime_to_format(mimetype) 517 | else: 518 | path="lod.page" 519 | ext="" 520 | 521 | if type_: 522 | if ext!='' : 523 | url=url_for(path, type_=type_, label=label, format_=ext) 524 | else: 525 | url=url_for(path, type_=type_, label=label) 526 | else: 527 | if ext!='': 528 | url=url_for(path, label=label, format_=ext) 529 | else: 530 | url=url_for(path, label=label) 531 | 532 | return redirect(url, 303) 533 | 534 | 535 | 536 | #@lod.route("/lodindex") # bound later 537 | def index(): 538 | 539 | return render_template("lodindex.html") 540 | 541 | @lod.route("/instances") 542 | def instances(): 543 | types=sorted([resolve(x) for x in current_app.config["types"]], key=lambda x: x['label'].lower()) 544 | resources={} 545 | for t in types: 546 | turl=t["realurl"] 547 | resources[turl]=sorted([resolve(x) for x in current_app.config["resources"][turl]][:10], 548 | key=lambda x: x.get('label').lower()) 549 | if len(current_app.config["resources"][turl])>10: 550 | resources[turl].append({ 'url': t["url"], 'external': False, 'label': "..." }) 551 | t["count"]=len(current_app.config["resources"][turl]) 552 | 553 | return render_template("instances.html", 554 | types=types, 555 | resources=resources) 556 | 557 | @lod.route("/search") 558 | def search(): 559 | 560 | searchterm=request.args["searchterm"] 561 | offset=int(request.args["offset"]) if "offset" in request.args else 0 562 | 563 | results=[] 564 | found=set() 565 | 566 | for resource, label in current_app.config["labels"].iteritems(): 567 | r=re.compile("\W%s\W"%re.escape(searchterm), re.I) 568 | if r.search(label): 569 | results.append(resolve(resource)) 570 | found.add(resource) 571 | if len(results)>offset+10: break 572 | 573 | results.sort(key=lambda x: x["label"].lower()) 574 | 575 | for resource, label in current_app.config["labels"].iteritems(): 576 | if len(results)>offset+10: break 577 | 578 | r=re.compile("%s"%re.escape(searchterm), re.I) 579 | if resource not in found and r.search(label): 580 | results.append(resolve(resource)) 581 | 582 | 583 | results=results[offset:offset+10] 584 | 585 | return render_template("search.html", 586 | searchterm=searchterm, 587 | results=results, 588 | offset=offset) 589 | 590 | 591 | @lod.before_request 592 | def setupSession(): 593 | if "picked" not in session: 594 | session["picked"]={} 595 | 596 | @lod.route("/pick") 597 | def pick(): 598 | u = request.args["uri"] 599 | if u in session["picked"]: 600 | del session["picked"][u] 601 | else: 602 | session["picked"][u]=True 603 | return redirect(request.referrer) 604 | 605 | @lod.route("/picked//") 606 | @lod.route("/picked//") 607 | @lod.route("/picked/") 608 | def picked(action=None, format_=None): 609 | 610 | def pickedgraph(): 611 | graph=rdflib.Graph() 612 | for p,ns in g.graph.namespaces(): 613 | graph.bind(p,ns) 614 | 615 | for x in session["picked"]: 616 | x = rdflib.URIRef(x) 617 | graph+=g.graph.triples((x,None,None)) 618 | graph+=g.graph.triples((None,None,x)) 619 | 620 | if not "notypes" in request.args and current_app.config["add_types_labels"]: 621 | _addTypesLabels(graph, g.graph) 622 | 623 | return graph 624 | 625 | 626 | 627 | if action=='download': 628 | return serialize(pickedgraph(), format_) 629 | elif action=='rdfgraph': 630 | return graphrdf(pickedgraph(), format_) 631 | elif action=='rdfsgraph': 632 | return graphrdfs(pickedgraph(), format_) 633 | elif action=='clear': 634 | session["picked"]={} 635 | return render_template("picked.html", 636 | things=[]) 637 | else: 638 | if action=='all': 639 | for t in current_app.config["resources"]: 640 | for x in current_app.config["resources"][t]: 641 | session["picked"][x]=True 642 | 643 | things=sorted([resolve(x) for x in session["picked"]]) 644 | return render_template("picked.html", 645 | things=things) 646 | 647 | ################## 648 | 649 | def serve(graph_,debug=False): 650 | """Serve the given graph on localhost with the LOD App""" 651 | 652 | get(graph_).run(debug=debug) 653 | 654 | 655 | def get(graph, types='auto',image_patterns=["\.[png|jpg|gif]$"], 656 | label_properties=LABEL_PROPERTIES, 657 | hierarchy_properties=[ RDFS.subClassOf, RDFS.subPropertyOf ], 658 | add_types_labels=True,dbname="RDFLib LOD App"): 659 | 660 | """ 661 | Get the LOD Flask App setup to serve the given graph 662 | """ 663 | 664 | app = Flask(__name__) 665 | 666 | app.config["graph"]=graph 667 | app.config["dbname"]=dbname 668 | 669 | app.config['types']=types 670 | 671 | app.config["js"]={ "endpoint": "static", "filename":"lod.js" } 672 | app.config["jssetup"]="lodsetup()" 673 | 674 | app.config["label_properties"]=label_properties 675 | app.config["hierarchy_properties"]=hierarchy_properties 676 | app.config["add_types_labels"]=add_types_labels 677 | 678 | app.register_blueprint(endpoint) 679 | app.register_blueprint(lod) 680 | 681 | app.add_url_rule('/', 'index', index) 682 | 683 | # make sure we get one session per app 684 | app.config["SESSION_COOKIE_NAME"]="SESSION_"+re.sub('[^a-zA-Z0-9_]','_', str(graph.identifier)) 685 | app.secret_key='veryverysecret'+str(graph.identifier) 686 | 687 | return app 688 | 689 | 690 | 691 | def _main(g, out, opts): 692 | import rdflib 693 | import sys 694 | 695 | dbname="commandline DB" 696 | 697 | if len(g)==0: 698 | import bookdb 699 | g=bookdb.bookdb 700 | dbname='Books DB' 701 | 702 | opts=dict(opts) 703 | debug='-d' in opts 704 | if '-N' in opts: 705 | dbname=opts["-N"] 706 | types='auto' 707 | if '-t' in opts: 708 | types=[rdflib.URIRef(x) for x in opts['-t'].split(',')] 709 | if '-n' in opts: 710 | types=None 711 | 712 | get(g, types=types, dbname=dbname).run(host="0.0.0.0", debug=debug) 713 | 714 | def main(): 715 | from rdflib.extras.cmdlineutils import main as cmdmain 716 | cmdmain(_main, options='t:ndN:', stdin=False) 717 | 718 | if __name__=='__main__': 719 | main() 720 | -------------------------------------------------------------------------------- /rdflib_web/mimeutils.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | import mimeparse 4 | except: 5 | import warnings 6 | warnings.warn("mimeparse not found - I need this for content negotiation, install with 'easy_install mimeparse'") 7 | mimeparse=None 8 | 9 | # sparql results 10 | JSON_MIME="application/sparql-results+json" 11 | XML_MIME="application/sparql-results+xml" 12 | 13 | HTML_MIME="text/html" 14 | N3_MIME="text/n3" 15 | TURTLE_MIME="text/turtle" 16 | RDFXML_MIME="application/rdf+xml" 17 | NTRIPLES_MIME="text/plain" 18 | JSONLD_MIME="application/json" 19 | 20 | FORMAT_MIMETYPE={ "rdf": RDFXML_MIME, "n3": N3_MIME, "nt": NTRIPLES_MIME, "turtle": TURTLE_MIME, "json-ld": JSONLD_MIME } 21 | MIMETYPE_FORMAT=dict(map(reversed,FORMAT_MIMETYPE.items())) 22 | 23 | def mime_to_format(mimetype): 24 | if mimetype in MIMETYPE_FORMAT: 25 | return MIMETYPE_FORMAT[mimetype] 26 | return "rdf" 27 | 28 | def format_to_mime(format): 29 | if format=='ttl': format='turtle' 30 | if format=='json': format='json-ld' 31 | if format in FORMAT_MIMETYPE: 32 | return format, FORMAT_MIMETYPE[format] 33 | return "xml", RDFXML_MIME 34 | 35 | 36 | 37 | def resultformat_to_mime(format): 38 | if format=='xml': return XML_MIME 39 | if format=='json': return JSON_MIME 40 | if format=='html': return HTML_MIME 41 | return "text/plain" 42 | 43 | def best_match(cand, header): 44 | if mimeparse and header: 45 | return mimeparse.best_match(cand,header) 46 | return None 47 | -------------------------------------------------------------------------------- /rdflib_web/static/jquery.tablesorter.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){$.extend({tablesorter:new 3 | function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i 2 | 3 | 4 | 19 | 21 | 23 | 27 | 31 | 32 | 34 | 38 | 42 | 43 | 52 | 53 | 71 | 73 | 74 | 76 | image/svg+xml 77 | 79 | 80 | 81 | 82 | 83 | 88 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /rdflib_web/static/star_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RDFLib/rdflib-web/10b40463e06e75d270e1c1a2ca164ad4b7f2d4fe/rdflib_web/static/star_inactive.png -------------------------------------------------------------------------------- /rdflib_web/static/star_inactive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 30 | 34 | 35 | 44 | 53 | 54 | 74 | 82 | 83 | 85 | 86 | 88 | image/svg+xml 89 | 91 | 92 | 93 | 94 | 95 | 100 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /rdflib_web/static/style.css: -------------------------------------------------------------------------------- 1 | div, html, body { 2 | border: 0; padding: 0; margin: 0; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | margin-bottom: 1cm; 8 | 9 | padding-top: 0; 10 | color: #333333; 11 | font: 13px/1.4 Helvetica,arial,freesans,clean,sans-serif; 12 | } 13 | 14 | table { border-collapse: collapse; } 15 | thead tr { border-bottom: 2px solid #aaa; cursor: pointer ; } 16 | th, td { text-align: left; padding: 10px; color: #333; } 17 | tbody tr { border-bottom: 1px solid #ccc; } 18 | tbody tr.even { background-color: #fafafa; } 19 | 20 | thead tr .headerSortUp { 21 | text-decoration: underline; 22 | } 23 | thead tr .headerSortUp:after { 24 | content: ' \2227'; 25 | } 26 | thead tr .headerSortDown { 27 | text-decoration: underline; 28 | } 29 | thead tr .headerSortDown:after { 30 | content: ' \2228'; 31 | } 32 | 33 | 34 | 35 | .pagenav { 36 | border-bottom: 1px solid #DDDDDD; 37 | min-height: 35px; 38 | height: 35px; 39 | margin: 10px 0; 40 | position: relative; 41 | padding: 0; 42 | } 43 | 44 | .pagenav .pagenav-tabs { 45 | display: inline-block; 46 | margin: 0; 47 | padding: 0; 48 | } 49 | 50 | .pagenav .pagenav-tabs > li { 51 | display: inline-block; 52 | padding: 0; 53 | margin: 0; 54 | } 55 | 56 | .pagenav .pagenav-tabs li img { 57 | height: 13px; } 58 | 59 | .pagenav-tab { 60 | -moz-border-bottom-colors: none; 61 | -moz-border-left-colors: none; 62 | -moz-border-right-colors: none; 63 | -moz-border-top-colors: none; 64 | border-color: #eee; 65 | border-image: none; 66 | border-style: solid solid none; 67 | border-width: 1px 1px 0; 68 | border-radius: 3px 3px 0 0; 69 | color: #666666; 70 | display: inline-block; 71 | font-size: 14px; 72 | margin-bottom: -1px; 73 | padding: 8px 12px; 74 | text-decoration: none; 75 | outline: 0; 76 | } 77 | 78 | .picker img { height: 12px; } 79 | 80 | .pagenav-tab.selected { 81 | background-color: #FFFFFF; 82 | border-color: #DDDDDD; 83 | border-radius: 3px 3px 0 0; 84 | color: #333333; 85 | font-weight: bold; 86 | } 87 | 88 | 89 | .pagenav ul li { 90 | padding-top: 8px; 91 | margin: 0 0 0 5px; 92 | } 93 | 94 | .pagenav ul li > li, ul.resources > li { 95 | border: medium none; 96 | border-radius: 0 0 0 0; 97 | position: relative; 98 | list-style-type: none; 99 | margin: 0 0 10px; 100 | overflow: hidden; 101 | } 102 | 103 | .pagenav ul li h3 { 104 | font-size: 20px; 105 | height: 32px; 106 | letter-spacing: -1px; 107 | line-height: 32px; 108 | margin-bottom: 1px; 109 | } 110 | 111 | .pagenav ul li h3 a { 112 | display: inline-block; 113 | line-height: 32px; 114 | position: relative; 115 | top: -5px; 116 | } 117 | 118 | 119 | .clearfix:after { 120 | clear: both; 121 | content: "."; 122 | display: block; 123 | height: 0; 124 | visibility: hidden; 125 | } 126 | 127 | .clearfix { 128 | display: block; 129 | } 130 | 131 | 132 | 133 | #header { 134 | padding: 10px; 135 | 136 | margin-top: 0; 137 | border-bottom: 1px solid #cacaca; 138 | box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 10px rgba(0, 0, 0, 0.1); 139 | } 140 | 141 | #header .heading { 142 | padding: 9px 0 0; 143 | color: #000000; 144 | text-decoration: none; 145 | font-weight: bold; 146 | font-size: 16pt; 147 | } 148 | 149 | #header .heading a { 150 | text-decoration: none; 151 | color: #000; 152 | } 153 | 154 | .container { 155 | margin: 0 auto; 156 | width: 920px; 157 | } 158 | 159 | 160 | 161 | #query { 162 | padding: 10px; 163 | display: inline-block; 164 | } 165 | 166 | a { 167 | color: #335599; 168 | } 169 | 170 | h1 { 171 | margin-top: 1cm; 172 | font-size: 1.5em; clear: both; 173 | } 174 | h2 { 175 | font-size: 1.2em; 176 | } 177 | h3 { 178 | font-size: 1em; 179 | } 180 | 181 | h1 .url { 182 | display: block; 183 | font-size: 0.5em; 184 | color: #aaa; 185 | } 186 | 187 | 188 | 189 | .comment { 190 | padding: 5px; 191 | padding-left: 10px; 192 | color: #555; 193 | background: #fafafa; 194 | border: 1px solid #eee; 195 | margin-left: 10px; 196 | margin-right: auto; 197 | margin-top: 10px; 198 | width: 650px; 199 | } 200 | 201 | #versions { 202 | font-size: 0.7em; 203 | } 204 | #footer { 205 | margin-top: 10px; 206 | background-color: #F8F8F8; 207 | border-bottom: 1px solid #E0E0E0; 208 | border-top: 1px solid #E0E0E0; 209 | box-shadow: 0 -1px 0 #FFFFFF inset; 210 | min-height: 160px; 211 | padding: 10px; 212 | } 213 | 214 | hr { 215 | border-color: #ddd; 216 | } 217 | 218 | #footermenu { 219 | margin-top: 1cm; 220 | } 221 | 222 | #versions, #footermenu { color: #555; } 223 | #versions a, #footermenu a { color: #6285cb; } 224 | #footermenu img { height: 16px; } 225 | 226 | #classmenu { 227 | margin-top: -10px; 228 | float: right; 229 | width: 400px; 230 | max-height: 500px; 231 | overflow-y: scroll; 232 | border-left: 1px solid #ddd; 233 | border-bottom: 1px solid #ddd; 234 | background: #eee; 235 | padding-left: 10px; 236 | } 237 | 238 | .termtype { 239 | color: #444; 240 | font-size: 80%; 241 | } 242 | 243 | .termtype a { 244 | color: #445; 245 | } 246 | 247 | .inlinelist ul li { 248 | float: left; 249 | display: block; 250 | margin-left: 5px; 251 | } 252 | 253 | .external:after { 254 | content: " \021f2;" ; 255 | vertical-align: super; 256 | font-size: 50%; 257 | } 258 | 259 | #search { 260 | float: right; 261 | } -------------------------------------------------------------------------------- /rdflib_web/templates/base.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block title %}{% endblock %} 10 | 11 | 12 | 13 | 14 | {% if config.js %} 15 | 16 | {% endif %} 17 | {% if config.jssetup %} 18 | 21 | {% endif %} 22 | 25 | 26 | 27 | 28 | 34 | 35 |
36 |
37 | {% block body %} 38 | {% endblock %} 39 |
40 |
41 | 42 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /rdflib_web/templates/class.html: -------------------------------------------------------------------------------- 1 | {% extends "classes.html" %} 2 | {% import 'utils.html' as utils %} 3 | 4 | {% block special %} 5 | {{super()}} 6 | 7 | {% if classinprops or classoutprops%} 8 |

Class Properties

9 | {% if classoutprops %} 10 |

⇒ properties

11 | 12 | 13 | 14 | 15 | {% for p,r in classoutprops %} 16 | 17 | {% if loop.first %}{% endif %} 18 | 19 | 20 | 21 | 22 | {% endfor %} 23 |
propertyrange
{{label}}{{utils.term(p)}}{{utils.term(r) if r else "(any)"}}
24 |
25 | {% endif %} 26 | 27 | 28 | {% if classinprops %} 29 |

⇐ properties

30 | 31 | 32 | 33 | 34 | {% for d,p in classinprops %} 35 | 36 | 37 | 38 | {% if loop.first %}{% endif %} 39 | 40 | 41 | {% endfor %} 42 | 43 | 44 | 45 |
domainproperty
{{utils.term(d) if d else "(any)" }}{{utils.term(p)}}{{label}}
46 | {% endif %} 47 | 48 | 49 | {% endif %} 50 | 51 | {% if instances %} 52 |

Instances

53 |
    54 | {% for x in instances %} 55 |
  • {{utils.term(x)}}
  • 56 | {% endfor %} 57 |
58 | {% endif %} 59 | 60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /rdflib_web/templates/classes.html: -------------------------------------------------------------------------------- 1 | {% extends "lodpage.html" %} 2 | 3 | {% block special %} 4 | 5 | {% if classes %} 6 |

Class Hierarchy

7 | 8 | {% import 'utils.html' as utils %} 9 | {{ utils.tree(classes) }} 10 | 11 | {% endif %} 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /rdflib_web/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}RDFLib SPARQL Endpoint{% endblock %} 4 | 5 | {% block body %} 6 | {% block intro %} 7 |

RDFLib SPARQL Endpoint

8 | 9 |

This is a SPARQL endpoint, conforming to the SPARQL Protocol powered by rdflib. Some extension from Joseki are also included.

10 | {% endblock %} 11 | 12 | {% block query %} 13 |

Query:

14 |
15 |
22 | 27 |
28 |
29 | 30 |
31 | {% endblock %} 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /rdflib_web/templates/instances.html: -------------------------------------------------------------------------------- 1 | {% extends "lodbase.html" %} 2 | {% import 'utils.html' as utils %} 3 | {% block body %} 4 | {{super()}} 5 | 6 |
7 |

Classes

8 |

jump to instances of a certain class

9 | {% if types[0].realurl!=None %} 10 | 11 |
    12 | {% for t in types %} 13 |
  • {{t.label}}
  • 14 | {% endfor %} 15 |
16 |
17 | 18 |

Instances

19 | {% for t in types %} 20 |

{{t.label}} ({{t.count}})

21 |
    22 | {% for r in resources[t.realurl] %} 23 |
  • {{utils.term(r)}}
  • 24 | {% endfor %} 25 |
26 | {% endfor %} 27 | {% else %} 28 | 29 |
    30 | {% for r in resources[None] %} 31 |
  • {{utils.term(r)}}
  • 32 | {% endfor %} 33 |
34 | 35 | 36 | {% endif %} 37 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /rdflib_web/templates/lodbase.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block header %} 4 |
5 | {{config["dbname"]}} 6 | 12 | 13 |
14 | {% endblock %} 15 | 16 | 17 | {% block body %} 18 | {{ super() }} 19 | 20 | 48 | 49 |
50 | {% endblock %} 51 | -------------------------------------------------------------------------------- /rdflib_web/templates/lodindex.html: -------------------------------------------------------------------------------- 1 | {% extends "lodbase.html" %} 2 | 3 | {% block title %}RDFLib LOD Server{% endblock %} 4 | 5 | {% block body %} 6 | {{super()}} 7 |

Welcome to the {{config["dbname"]}} RDFLib LOD Server

8 | 9 |

This is a Linked Open Data like server for rdflib. 10 | A SPARQL endpoint, conforming to the SPARQL Protocol with Joseki extensions is also included.

11 | 12 | 13 |
14 | 15 |

Bulk Download

16 | 17 |

You can download this entire dataset as: 18 | 19 |

24 |
25 | 26 | {% block query %} 27 |

Query:

28 |
29 |
36 | 41 |
42 |
43 | 44 |
45 | {% endblock %} 46 | 47 | 48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /rdflib_web/templates/lodpage.html: -------------------------------------------------------------------------------- 1 | {% extends "lodbase.html" %} 2 | {% import 'utils.html' as utils %} 3 | 4 | {% block title %}{{label}} @ {{config["dbname"]}}{% endblock %} 5 | 6 | {% block body %} 7 | {{super()}} 8 | 9 | 10 |

11 | 12 | {{label}} 13 | {% if picked %} 14 | 15 | {% else %} 16 | 17 | {% endif %} 18 | 19 | {{resource}} 20 |

21 | is-a 22 | 23 | {% for t in types %} 24 | {{utils.term(t)}} 25 | {% if t.realurl.__str__() == "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property" %} 26 | ( {{ utils.term(domain) }} ⇒ {{ utils.term(range) }} ) 27 | {% endif %} 28 | {% if not loop.last %}, {% endif %} 29 | {% endfor %} 30 | 31 | 32 | {% for c in comments %} 33 |
{{c|safe}}
34 | {% endfor %} 35 | 36 | {% block special %}{% endblock %} 37 | {% if outprops %} 38 |

Properties:

39 | 40 | 41 | 42 | 43 | {% for p,v in outprops %} 44 | 45 | 46 | {% if v.url %} 47 | 48 | {% else %} 49 | 53 | {% endfor %} 54 |
propertyvalue
{{utils.term(p)}}{{utils.term(v, withclass=True)}}"{{v.label}}" 50 | {% if v.lang %} ({{v.lang}}) {% endif %} 51 | {% endif %} 52 |
55 | {% endif %} 56 | {% if inprops %} 57 |

Incoming Properties:

58 | 59 | 60 | 61 | 62 | {% for v,p in inprops %} 63 | 64 | 65 | {% if p.url %} 66 | 67 | {% else %} 68 | 71 | {% endfor %} 72 |
nodeproperty
{{utils.term(v, withclass=True)}}{{utils.term(p)}}{{p.label}} 69 | {% endif %} 70 |
73 | {% endif %} 74 | 75 |
76 | 77 | Download as: 78 | {% if type_ %} 79 | ntriples | 80 | turtle | 81 | rdf/xml | 82 | json-ld 83 | Graph: 84 | png | 85 | pdf | 86 | svg | 87 | dot 88 | 89 | {% else %} 90 | 91 | ntriples | 92 | n3 | 93 | rdf/xml | 94 | json-ld 95 | | 96 | Graph: 97 | png | 98 | pdf | 99 | svg | 100 | dot 101 | 102 | {% endif %} 103 |
104 | 105 | {% endblock %} 106 | -------------------------------------------------------------------------------- /rdflib_web/templates/picked.html: -------------------------------------------------------------------------------- 1 | {% extends "lodbase.html" %} 2 | {% import 'utils.html' as utils %} 3 | {% block body %} 4 | {{super()}} 5 |

Picked resources:

6 | 7 |

Starring resources lets you export or graph several resources as once, click the star icon next to labels of a few things, then come here and export or graph them together. 8 |
9 | Click the stars in the list below to deselect individual resources. 10 |

11 | 12 |
    13 | {% if things %} 14 | {% for r in things %} 15 |
  • 16 | {{utils.term(r)}}
  • 17 | {% endfor %} 18 | {% else %} 19 |
  • Nothing picked
  • 20 | {% endif %} 21 |
22 | 23 | clear all 24 | select all 25 | 26 | 27 |

Download as:

28 |
    29 | {% for f in ("xml", "turtle", "nt") %} 30 |
  • {{f}}
  • 31 | {% endfor %} 32 |
33 |
34 |
35 | 36 |

RDF Graph:

37 |

Draw the RDF graph.

38 |
    39 | {% for f in ("png","svg","pdf","dot") %} 40 |
  • {{f}}
  • 41 | {% endfor %} 42 |
43 |
44 |
45 | 46 |

RDFSchema Graph:

47 |

Draw the a diagram of the RDFS Classes and Properties, if you did not select any - the graph may be empty!

48 |
    49 | {% for f in ("png","svg","pdf","dot") %} 50 |
  • {{f}}
  • 51 | {% endfor %} 52 |
53 |
54 |
55 | 56 |
57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /rdflib_web/templates/properties.html: -------------------------------------------------------------------------------- 1 | {% extends "classes.html" %} 2 | 3 | {% block special %} 4 | 5 | {{ super() }} 6 | 7 | {% if properties %} 8 |

Property Hierarchy

9 | 10 | {% import 'utils.html' as utils %} 11 | {{ utils.tree(properties) }} 12 | 13 | {% endif %} 14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /rdflib_web/templates/property.html: -------------------------------------------------------------------------------- 1 | {% extends "lodpage.html" %} 2 | 3 | {% block special %} 4 | 5 | {% if properties %} 6 |

Property Hierarchy

7 | 8 | {% import 'utils.html' as utils %} 9 | {{ utils.tree(properties) }} 10 | 11 | {% endif %} 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /rdflib_web/templates/results.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}RDFLib SPARQL Endpoint - Results{% endblock %} 4 | 5 | {% block body %} 6 | 7 |

Query Results

8 | 9 |
10 | {{q}}
11 | 
12 | 13 | {{results}} 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /rdflib_web/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "lodbase.html" %} 2 | {% import 'utils.html' as utils %} 3 | {% block body %} 4 | {{super()}} 5 |

Search for '{{searchterm}}'

6 | 7 | {% if not results %} 8 |

no (more) results

9 | {% else %} 10 | (results {{offset}} to {{offset+10}}) 11 | 12 | 13 | 14 | 15 | {% for x in results %} 16 | 17 | 18 | 19 | {% endfor %} 20 |
resource
{{utils.term(x, withclass=True)}}
21 |
22 | {% if offset>0 %} 23 | < < previous 24 | {% endif %} 25 | next >> 26 |
27 | {% endif %} 28 | 29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /rdflib_web/templates/utils.html: -------------------------------------------------------------------------------- 1 | {% macro tree(items) %} 2 |
    3 | {% for node,children in items recursive %} 4 | {% if node is mapping %} 5 | {% set node=[node] %} 6 | {% endif %} 7 |
  • 8 | {{ term(node) }} 9 | {% if children %} 10 |
      11 | {{ loop(children) }} 12 |
    13 | {% endif %} 14 |
  • 15 | {% endfor %} 16 |
17 | {% endmacro %} 18 | 19 | {% macro term(t, withpicker=True, withclass=False) %} 20 | 21 | {% if t is not mapping %} 22 | 23 | {% for x in t %} 24 | {{ term(x, withpicker, withclass) }} 25 | {{ ", " if not loop.last }} 26 | {% endfor %} 27 | 28 | {% else %} 29 | 30 | {{t.label}} 31 | {% if withpicker %} 32 | 33 | {% if t.picked %} 34 | 35 | {% else %} 36 | 37 | {% endif %} 38 | {% endif %} 39 | 40 | 41 | {% if withclass and t.type%} 42 |
43 | a {{term(t.type, withpicker=False)}} 44 | {% endif %} 45 | 46 | {% endif %} 47 | 48 | {% endmacro %} 49 | -------------------------------------------------------------------------------- /run_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Testing with Nose 5 | ================= 6 | 7 | This test runner uses Nose for test discovery and running. It uses the argument 8 | spec of Nose, but with some options pre-set. To begin with, make sure you have 9 | Nose installed, e.g.: 10 | 11 | $ sudo easy_install nose 12 | 13 | For daily test runs, use: 14 | 15 | $ ./run_tests.py 16 | 17 | If you supply attributes, the default ones defined in ``DEFAULT_ATTRS`` will be 18 | ignored. So to run e.g. all tests marked ``slowtest`` or ``non_standard_dep``, 19 | do: 20 | 21 | $ ./run_tests.py -a slowtest,non_standard_dep 22 | 23 | See for furher details. An excellent 24 | article is also available at . 25 | 26 | Note that this is just a convenience script. You can use ``nosetests`` directly 27 | if it's on $PATH, with the difference that you have to supply the options 28 | pre-set here manually. 29 | 30 | Coverage 31 | ======== 32 | 33 | If ``coverage.py`` is placed in $PYTHONPATH, it can be used to create coverage 34 | information (using the built-in coverage plugin of Nose) if the default 35 | option "--with-coverage" is supplied (which also enables some additional 36 | coverage options). 37 | 38 | See for details. 39 | 40 | """ 41 | 42 | 43 | NOSE_ARGS = [ 44 | '--where=./', 45 | #'--with-doctest', 46 | #'--doctest-extension=.doctest', 47 | #'--doctest-tests', 48 | #'--with-coverage', 49 | #'--enable-cover', 50 | #'--enable-audit', 51 | #'--trim-errors', 52 | #'--with-EARL', 53 | #'--with-xunit', 54 | ] 55 | 56 | COVERAGE_EXTRA_ARGS = [ 57 | '--cover-inclusive', 58 | '--cover-html', 59 | '--cover-html-dir=coverage', 60 | ] 61 | 62 | DEFAULT_ATTRS = ['!known_issue', '!performancetest', '!storetest'] # ['!known_issue', '!sparql'] 63 | 64 | DEFAULT_DIRS = ['test'] 65 | 66 | 67 | if __name__ == '__main__': 68 | 69 | from sys import argv, exit, stderr 70 | try: import nose 71 | except ImportError: 72 | print >>stderr, """\ 73 | Requires Nose. Try: 74 | 75 | $ sudo easy_install nose 76 | 77 | Exiting. """; exit(1) 78 | 79 | 80 | if '--with-coverage' in argv + NOSE_ARGS: 81 | try: import coverage 82 | except ImportError: 83 | print >>stderr, "No coverage module found, skipping code coverage." 84 | argv.remove('--with-coverage') 85 | else: 86 | NOSE_ARGS += COVERAGE_EXTRA_ARGS 87 | 88 | 89 | if True not in [a.startswith('-a') or a.startswith('--attr=') for a in argv]: 90 | argv.append('--attr=' + ','.join(DEFAULT_ATTRS)) 91 | 92 | if not [a for a in argv[1:] if not a.startswith('-')]: 93 | argv += DEFAULT_DIRS # since nose doesn't look here by default.. 94 | 95 | 96 | finalArgs = NOSE_ARGS + argv 97 | print "Running nose with:", " ".join(finalArgs[1:]) 98 | nose.run_exit(argv=finalArgs) 99 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build=.dev 3 | tag_date=1 4 | 5 | [nosetests] 6 | 7 | attr = !known_issue,!slow,!non_core,!manual,!performancetest 8 | verbosity = 2 9 | #with-doctest = 1 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import re 5 | 6 | def setup_python3(): 7 | # Taken from "distribute" setup.py 8 | from distutils.filelist import FileList 9 | from distutils import dir_util, file_util, util, log 10 | from os.path import join 11 | 12 | tmp_src = join("build", "src") 13 | log.set_verbosity(1) 14 | fl = FileList() 15 | for line in open("MANIFEST.in"): 16 | if not line.strip(): 17 | continue 18 | fl.process_template_line(line) 19 | dir_util.create_tree(tmp_src, fl.files) 20 | outfiles_2to3 = [] 21 | for f in fl.files: 22 | outf, copied = file_util.copy_file(f, join(tmp_src, f), update=1) 23 | if copied and outf.endswith(".py"): 24 | outfiles_2to3.append(outf) 25 | 26 | util.run_2to3(outfiles_2to3) 27 | 28 | # arrange setup to use the copy 29 | sys.path.insert(0, tmp_src) 30 | 31 | return tmp_src 32 | 33 | # Find version. We have to do this because we can't import it in Python 3 until 34 | # its been automatically converted in the setup process. 35 | def find_version(filename): 36 | _version_re = re.compile(r'__version__ = "(.*)"') 37 | for line in open(filename): 38 | version_match = _version_re.match(line) 39 | if version_match: 40 | return version_match.group(1) 41 | 42 | __version__ = find_version('rdflib_web/__init__.py') 43 | 44 | config = dict( 45 | name = 'rdflib-web', 46 | version = __version__, 47 | description = "RDFLib Web Apps.", 48 | author = "Gunnar Aastrand Grimnes", 49 | author_email = "gromgull@gmail.com", 50 | url = "https://github.com/RDFLib/rdflib-web", 51 | license = "BSD", 52 | platforms = ["any"], 53 | classifiers = ["Programming Language :: Python", 54 | "License :: OSI Approved :: BSD License", 55 | "Topic :: Software Development :: Libraries :: Python Modules", 56 | "Operating System :: OS Independent", 57 | ], 58 | packages = ['rdflib_web'], 59 | package_dir = { 'rdflib_web': 'rdflib_web' }, 60 | package_data = { 'rdflib_web': ['templates/*.html','static/*',]} 61 | ) 62 | 63 | if sys.version_info[0] >= 3: 64 | from setuptools import setup 65 | config.update({'use_2to3': True}) 66 | config.update({'src_root': setup_python3()}) 67 | else: 68 | try: 69 | from setuptools import setup 70 | config.update({'test_suite' : "nose.collector"}) 71 | except ImportError: 72 | from distutils.core import setup 73 | 74 | 75 | install_requires = [ 76 | 'flask', 77 | 'rdflib>=4.0', 78 | ] 79 | 80 | tests_require = install_requires 81 | 82 | extras_require = { 83 | "web-conneg": ["mimeparse"], 84 | 85 | } 86 | 87 | 88 | config.update( 89 | entry_points = { 90 | 'console_scripts': [ 91 | 'rdfsparqlapp = rdflib_web.endpoint:main', 92 | 'rdflodapp = rdflib_web.lod:main', 93 | ], 94 | 'rdf.plugins.serializer': [ 95 | 'html = rdflib_web.htmlresults:HTMLSerializer', 96 | ], 97 | 'rdf.plugins.resultserializer': [ 98 | 'html = rdflib_web.htmlresults:HTMLResultSerializer', 99 | ], 100 | 101 | }, 102 | install_requires = install_requires, 103 | tests_require = tests_require, 104 | extras_require = extras_require 105 | ) 106 | 107 | setup(**config) 108 | 109 | -------------------------------------------------------------------------------- /test/endpoint_protocol/README.md: -------------------------------------------------------------------------------- 1 | # HTTP based testcases 2 | 3 | This directory contains HTTP based tests described in a restricted 4 | github flavoured markdown syntax. `../test_endpoint_protocol.py` 5 | extracts the tests from these files and runs them. 6 | 7 | ## Filename 8 | 9 | A testcase file must have a name starting with 'test_'. The part of 10 | the filename before '.' is used as testcase name and must be a valid 11 | Python identifier. 12 | 13 | ## Syntax 14 | 15 | Example: 16 | 17 | # Name of the testcase [rdflib.Graph()] 18 | 19 | The part between [ ] in the title above is evaluated in order to 20 | create the endpoint's dataset for this test case. It defaults to 21 | `rdflib.Dataset()`. 22 | 23 | Description of the testcase goes here. 24 | 25 | ## name_of_test 26 | 27 | The parser looks for a line starting with `##` to find the first 28 | test. Then it skips everything until the first `### Request` line, 29 | i.e. here is some space for a description. 30 | 31 | ### Request 32 | 33 | In this section the client's request has to be described inside a 34 | codeblock. Only a single codeblock is allowed. It must be enclosed 35 | two lines only containing three backticks. 36 | 37 | ``` 38 | POST /foo/bar/baz HTTP/1.1 39 | Host: $HOST$ 40 | Content-type: text/plain 41 | 42 | Some body 43 | ``` 44 | 45 | ### Response 46 | 47 | Here, the codeblock must contain the expected response. 48 | 49 | ``` 50 | 201 Created 51 | Location: http://example.org/newres 52 | ``` 53 | 54 | 55 | ## name_of_second_test 56 | 57 | ### Request 58 | 59 | Here, the variable $NEWPATH$ is replaced with the value of the 60 | Location header from the last response that contained such a 61 | header. 62 | 63 | ``` 64 | GET $NEWPATH$ HTTP/1.1 65 | Host: $HOST$ 66 | Accept: text/turtle 67 | ``` 68 | 69 | ### Response 70 | 71 | ``` 72 | 200 Ok 73 | Conent-type: text/plain 74 | 75 | Some body 76 | ``` 77 | 78 | ## Variables 79 | 80 | The following variables are substituted: 81 | 82 | * `$HOST$` is the host where the endpoint is listening 83 | * `$GRAPHSTORE$` is the path of the URL of the graph store 84 | * `$NEWPATH$` is the URL returned in the Location HTTP header 85 | 86 | ## Verification of Responses 87 | 88 | The test runner compares codes, but it does not report differing 89 | status messages. It verifies whether all headers given in the test are 90 | present in the response and compares their values. It permits 91 | additional headers in the response. If the bodies are of type 92 | text/turtle, then it checks them for equivalence. Otherwise, they are 93 | compared as text. 94 | 95 | -------------------------------------------------------------------------------- /test/endpoint_protocol/test_graphstore_w3c.md: -------------------------------------------------------------------------------- 1 | # Graph Store Protocol Tests 2 | 3 | This is a modified version of 4 | http://www.w3.org/2009/sparql/docs/tests/data-sparql11/http-rdf-update/ 5 | See also: http://www.w3.org/2009/sparql/docs/tests/README.html 6 | 7 | It is indicated where the tests differ from the original version. 8 | 9 | ## put__initial_state 10 | 11 | PUT - Initial state 12 | 13 | ### Request 14 | 15 | ``` 16 | PUT $GRAPHSTORE$/person/1.ttl HTTP/1.1 17 | Host: $HOST$ 18 | Content-Type: text/turtle; charset=utf-8 19 | 20 | @prefix foaf: . 21 | @prefix v: . 22 | 23 | a foaf:Person; 24 | foaf:businessCard [ 25 | a v:VCard; 26 | v:fn "John Doe" 27 | ]. 28 | ``` 29 | 30 | ### Response 31 | 32 | ``` 33 | 201 Created 34 | ``` 35 | 36 | ## get_of_put__initial_state 37 | 38 | GET of PUT - Initial state 39 | 40 | ### Request 41 | 42 | ``` 43 | GET $GRAPHSTORE$?graph=http://$HOST$$GRAPHSTORE$/person/1.ttl HTTP/1.1 44 | Host: $HOST$ 45 | Accept: text/turtle 46 | ``` 47 | 48 | 49 | ### Response 50 | 51 | ``` 52 | 200 OK 53 | Content-Type: text/turtle; charset=utf-8 54 | Content-Length: ... 55 | 56 | @prefix foaf: . 57 | @prefix v: . 58 | 59 | a foaf:Person; 60 | foaf:businessCard [ 61 | a v:VCard; 62 | v:fn "John Doe" 63 | ]. 64 | ``` 65 | 66 | ## head_on_an_existing_graph 67 | 68 | HEAD on an existing graph 69 | 70 | ### Request 71 | 72 | ``` 73 | HEAD $GRAPHSTORE$/person/1.ttl HTTP/1.1 74 | Host: $HOST$ 75 | Accept: text/turtle 76 | ``` 77 | 78 | 79 | ### Response 80 | 81 | ``` 82 | 200 OK 83 | Content-Type: text/turtle; charset=utf-8 84 | Content-Length: ... 85 | ``` 86 | 87 | ## head_on_a_nonexisting_graph 88 | 89 | HEAD on a non-existing graph 90 | 91 | ### Request 92 | 93 | ``` 94 | HEAD $GRAPHSTORE$/person/4.ttl HTTP/1.1 95 | Host: $HOST$ 96 | ``` 97 | 98 | 99 | ### Response 100 | 101 | ``` 102 | 404 Not Found 103 | ``` 104 | 105 | 106 | ## put__graph_already_in_store 107 | 108 | PUT - graph already in store 109 | 110 | ### Request 111 | 112 | ``` 113 | PUT $GRAPHSTORE$/person/1.ttl HTTP/1.1 114 | Host: $HOST$ 115 | Content-Type: text/turtle; charset=utf-8 116 | 117 | @prefix foaf: . 118 | @prefix v: . 119 | 120 | a foaf:Person; 121 | foaf:businessCard [ 122 | a v:VCard; 123 | v:fn "Jane Doe" 124 | ]. 125 | ``` 126 | 127 | ### Response 128 | 129 | ``` 130 | 204 No Content 131 | ``` 132 | 133 | ## get_of_put__graph_already_in_store 134 | 135 | GET of PUT - graph already in store 136 | 137 | ### Request 138 | 139 | ``` 140 | GET $GRAPHSTORE$/person/1.ttl HTTP/1.1 141 | Host: $HOST$ 142 | Accept: text/turtle 143 | ``` 144 | 145 | 146 | ### Response 147 | 148 | ``` 149 | 200 OK 150 | Content-Type: text/turtle; charset=utf-8 151 | Content-Length: ... 152 | 153 | @prefix foaf: . 154 | @prefix v: . 155 | 156 | a foaf:Person; 157 | foaf:businessCard [ 158 | a v:VCard; 159 | v:fn "Jane Doe" 160 | ] . 161 | ``` 162 | 163 | ## put__default_graph 164 | 165 | PUT - default graph 166 | 167 | ### Request 168 | 169 | ``` 170 | PUT $GRAPHSTORE$?default HTTP/1.1 171 | Host: $HOST$ 172 | Content-Type: text/turtle; charset=utf-8 173 | 174 | @prefix foaf: . 175 | @prefix v: . 176 | 177 | [] a foaf:Person; 178 | foaf:businessCard [ 179 | a v:VCard; 180 | v:given-name "Alice" 181 | ] . 182 | ``` 183 | 184 | ### Response 185 | rdflib: Changed from '201 Created' to '204 No Content' because the 186 | default graph always already exists. 187 | 188 | ``` 189 | 204 No Content 190 | ``` 191 | 192 | ## get_of_put__default_graph 193 | 194 | GET of PUT - default graph 195 | 196 | ### Request 197 | 198 | ``` 199 | GET $GRAPHSTORE$?default HTTP/1.1 200 | Host: $HOST$ 201 | Accept: text/turtle 202 | ``` 203 | 204 | 205 | ### Response 206 | 207 | ``` 208 | 200 OK 209 | Content-Type: text/turtle; charset=utf-8 210 | Content-Length: ... 211 | 212 | @prefix foaf: . 213 | @prefix v: . 214 | 215 | [] a foaf:Person; 216 | foaf:businessCard [ 217 | a v:VCard; 218 | v:given-name "Alice" 219 | ] . 220 | ``` 221 | 222 | > Broken test. This request is good like this. 223 | > ## put__mismatched_payload 224 | > PUT - mismatched payload 225 | > ### Request 226 | > ``` 227 | > PUT $GRAPHSTORE$/person/1.ttl HTTP/1.1 228 | > Host: $HOST$ 229 | > Content-Type: text/turtle; charset=utf-8 230 | > 231 | > @prefix foaf: . 232 | > @prefix v: . 233 | > 234 | > a foaf:Person; 235 | > foaf:businessCard [ 236 | > a v:VCard; 237 | > v:fn "Jane Doe" 238 | > ]. 239 | > ``` 240 | > ### Response 241 | > ``` 242 | > 400 Bad Request 243 | > ``` 244 | 245 | ## put__empty_graph 246 | 247 | PUT - empty graph 248 | 249 | ### Request 250 | 251 | ``` 252 | PUT $GRAPHSTORE$?graph=http://$HOST$$GRAPHSTORE$/person/2.ttl HTTP/1.1 253 | Host: $HOST$ 254 | Content-Type: text/turtle; charset=utf-8 255 | ``` 256 | 257 | 258 | ### Response 259 | 260 | rdflib: Changed from '200 OK' to '201 Created' because 261 | the graph 2.ttl does not yet exist. (Although the 262 | specification is a bit fishy in this regard.) 263 | 264 | ``` 265 | 201 Created 266 | ``` 267 | 268 | 269 | ## get_of_put__empty_graph 270 | 271 | GET of PUT - empty graph 272 | 273 | ### Request 274 | 275 | ``` 276 | GET $GRAPHSTORE$/person/2.ttl HTTP/1.1 277 | Host: $HOST$ 278 | Accept: text/turtle 279 | ``` 280 | 281 | 282 | ### Response 283 | 284 | ``` 285 | 200 OK 286 | Content-Type: text/turtle; charset=utf-8 287 | Content-Length: ... 288 | ``` 289 | 290 | 291 | ## put__replace_empty_graph 292 | PUT - replace empty graph 293 | ### Request 294 | 295 | ``` 296 | PUT $GRAPHSTORE$?graph=http://$HOST$$GRAPHSTORE$/person/2.ttl HTTP/1.1 297 | Host: $HOST$ 298 | Content-Type: text/turtle; charset=utf-8 299 | 300 | @prefix foaf: . 301 | @prefix v: . 302 | 303 | [] a foaf:Person; 304 | foaf:businessCard [ 305 | a v:VCard; 306 | v:given-name "Alice" 307 | ] . 308 | ``` 309 | 310 | ### Response 311 | 312 | rdflib: Changed from '200 OK' to '204 No Content' because the server 313 | does not send a body. 314 | 315 | ``` 316 | 204 No Content 317 | ``` 318 | 319 | ## get_of_replacement_for_empty_graph 320 | GET of replacement for empty graph 321 | ### Request 322 | 323 | ``` 324 | GET $GRAPHSTORE$/person/2.ttl HTTP/1.1 325 | Host: $HOST$ 326 | Accept: text/turtle 327 | ``` 328 | 329 | ### Response 330 | 331 | ``` 332 | 200 OK 333 | Content-Type: text/turtle; charset=utf-8 334 | Content-Length: ... 335 | 336 | @prefix foaf: . 337 | @prefix v: . 338 | 339 | [] a foaf:Person; 340 | foaf:businessCard [ 341 | a v:VCard; 342 | v:given-name "Alice" 343 | ] . 344 | ``` 345 | 346 | 347 | 348 | ## delete__existing_graph 349 | DELETE - existing graph 350 | ### Request 351 | 352 | ``` 353 | DELETE $GRAPHSTORE$/person/2.ttl HTTP/1.1 354 | Host: $HOST$ 355 | ``` 356 | 357 | ### Response 358 | 359 | rdflib: Changed from '200 OK' to '204 No Content' for 360 | obvious reasons. 361 | 362 | ``` 363 | 204 No Content 364 | ``` 365 | 366 | ## get_of_delete__existing_graph 367 | GET of DELETE - existing graph 368 | ### Request 369 | 370 | ``` 371 | GET $GRAPHSTORE$/person/2.ttl HTTP/1.1 372 | Host: $HOST$ 373 | Accept: text/turtle 374 | ``` 375 | 376 | ### Response 377 | 378 | ``` 379 | 404 Not Found 380 | ``` 381 | 382 | ## delete__nonexistent_graph 383 | DELETE - non-existent graph 384 | ### Request 385 | 386 | ``` 387 | DELETE $GRAPHSTORE$/person/2.ttl HTTP/1.1 388 | Host: $HOST$ 389 | ``` 390 | 391 | ### Response 392 | 393 | ``` 394 | 404 Not Found 395 | ``` 396 | 397 | ## post__existing_graph 398 | POST - existing graph 399 | ### Request 400 | 401 | ``` 402 | POST $GRAPHSTORE$/person/1.ttl HTTP/1.1 403 | Host: $HOST$ 404 | Content-Type: text/turtle; charset=utf-8 405 | 406 | @prefix foaf: . 407 | 408 | foaf:name "Jane Doe" . 409 | ``` 410 | 411 | ### Response 412 | 413 | rdflib: Changed from '200 OK' to '204 No Content'. 414 | 415 | ``` 416 | 204 No Content 417 | ``` 418 | 419 | ## get_of_post__existing_graph 420 | 421 | GET of POST - existing graph 422 | 423 | ### Request 424 | 425 | ``` 426 | GET $GRAPHSTORE$/person/1.ttl HTTP/1.1 427 | Host: $HOST$ 428 | Accept: text/turtle 429 | ``` 430 | 431 | 432 | ### Response 433 | 434 | ``` 435 | 200 OK 436 | Content-Type: text/turtle; charset=utf-8 437 | Content-Length: ... 438 | 439 | @prefix foaf: . 440 | @prefix v: . 441 | 442 | a foaf:Person; 443 | foaf:name "Jane Doe"; 444 | foaf:businessCard [ 445 | a v:VCard; 446 | v:fn "Jane Doe" 447 | ] . 448 | ``` 449 | 450 | ## post__multipart_formdata 451 | 452 | POST - multipart/form-data 453 | 454 | ### Request 455 | 456 | ``` 457 | POST $GRAPHSTORE$/person/1.ttl HTTP/1.1 458 | Host: $HOST$ 459 | Content-Type: multipart/form-data; boundary=a6fe4cd636164618814be9f8d3d1a0de 460 | 461 | --a6fe4cd636164618814be9f8d3d1a0de 462 | Content-Disposition: form-data; name="lastName.ttl"; filename="lastName.ttl" 463 | Content-Type: text/turtle; charset=utf-8 464 | 465 | @prefix foaf: . 466 | foaf:familyName "Doe" . 467 | 468 | --a6fe4cd636164618814be9f8d3d1a0de 469 | Content-Disposition: form-data; name="firstName.ttl"; filename="firstName.ttl" 470 | Content-Type: text/turtle; charset=utf-8 471 | 472 | @prefix foaf: . 473 | foaf:givenName "Jane" . 474 | 475 | --a6fe4cd636164618814be9f8d3d1a0de-- 476 | ``` 477 | 478 | ### Response 479 | rdflib: Changed from '200 OK' to '204 No Content'. 480 | 481 | ``` 482 | 204 No Content 483 | ``` 484 | 485 | ## get_of_post__multipart_formdata 486 | 487 | GET of POST - multipart/form-data 488 | 489 | ### Request 490 | 491 | ``` 492 | GET $GRAPHSTORE$/person/1.ttl HTTP/1.1 493 | Host: $HOST$ 494 | Accept: text/turtle 495 | ``` 496 | 497 | 498 | ### Response 499 | 500 | ``` 501 | 200 OK 502 | Content-Type: text/turtle; charset=utf-8 503 | Content-Length: ... 504 | 505 | @prefix foaf: . 506 | @prefix v: . 507 | 508 | a foaf:Person; 509 | foaf:name "Jane Doe"; 510 | foaf:givenName "Jane"; 511 | foaf:familyName "Doe"; 512 | foaf:businessCard [ 513 | a v:VCard; 514 | v:fn "Jane Doe" 515 | ] . 516 | ``` 517 | 518 | ## post__create__new_graph 519 | 520 | POST - create new graph 521 | 522 | ### Request 523 | 524 | ``` 525 | POST $GRAPHSTORE$ HTTP/1.1 526 | Host: $HOST$ 527 | Content-Type: text/turtle; charset=utf-8 528 | 529 | @prefix foaf: . 530 | @prefix v: . 531 | 532 | [] a foaf:Person; 533 | foaf:businessCard [ 534 | a v:VCard; 535 | v:given-name "Alice" 536 | ] . 537 | ``` 538 | 539 | ### Response 540 | 541 | ``` 542 | 201 Created 543 | Location: $NEWPATH$ 544 | ``` 545 | 546 | 547 | ## get_of_post__create__new_graph 548 | 549 | GET of POST - create new graph 550 | 551 | ### Request 552 | 553 | ``` 554 | GET $NEWPATH$ HTTP/1.1 555 | Host: $HOST$ 556 | Accept: text/turtle 557 | ``` 558 | 559 | 560 | ### Response 561 | 562 | ``` 563 | 200 OK 564 | Content-Type: text/turtle; charset=utf-8 565 | Content-Length: ... 566 | 567 | @prefix foaf: . 568 | @prefix v: . 569 | 570 | [] a foaf:Person; 571 | foaf:businessCard [ 572 | a v:VCard; 573 | v:given-name "Alice" 574 | ] . 575 | ``` 576 | 577 | ## post__empty_graph_to_existing_graph 578 | 579 | POST - empty graph to existing graph 580 | 581 | ### Request 582 | 583 | ``` 584 | POST $NEWPATH$ HTTP/1.1 585 | Host: $HOST$ 586 | Content-Type: text/turtle; charset=utf-8 587 | ``` 588 | 589 | 590 | ### Response 591 | 592 | ``` 593 | 204 No Content 594 | ``` 595 | 596 | ## get_of_post__after_noop 597 | 598 | GET of POST - after noop 599 | 600 | ### Request 601 | 602 | ``` 603 | GET $NEWPATH$ HTTP/1.1 604 | Host: $HOST$ 605 | Accept: text/turtle 606 | ``` 607 | 608 | 609 | ### Response 610 | 611 | ``` 612 | 200 OK 613 | Content-Type: text/turtle; charset=utf-8 614 | Content-Length: ... 615 | 616 | @prefix foaf: . 617 | @prefix v: . 618 | 619 | [] a foaf:Person; 620 | foaf:businessCard [ 621 | a v:VCard; 622 | v:given-name "Alice" 623 | ] . 624 | ``` 625 | 626 | -------------------------------------------------------------------------------- /test/endpoint_protocol/test_graphstore_with_ConjunctiveGraph.md: -------------------------------------------------------------------------------- 1 | # Graph Store Protocol Tests [rdflib.ConjunctiveGraph()] 2 | 3 | Testing the graph store protocol implementation backed by a plain 4 | Graph. The graph is served as default graph and no other graphs can be 5 | created. 6 | 7 | ## put__default_graph 8 | 9 | ### Request 10 | 11 | ``` 12 | PUT $GRAPHSTORE$?default HTTP/1.1 13 | Host: $HOST$ 14 | Content-Type: text/turtle; charset=utf-8 15 | 16 | @prefix foaf: . 17 | @prefix v: . 18 | 19 | a foaf:Person; 20 | foaf:businessCard [ 21 | a v:VCard; 22 | v:fn "John Doe" 23 | ]. 24 | ``` 25 | 26 | ### Response 27 | 28 | ``` 29 | 400 Default graph is read only 30 | 31 | ``` 32 | 33 | ## get_of_put__default_graph 34 | 35 | ### Request 36 | 37 | ``` 38 | GET $GRAPHSTORE$?default HTTP/1.1 39 | Host: $HOST$ 40 | Accept: text/turtle 41 | 42 | ``` 43 | 44 | ### Response 45 | 46 | ``` 47 | 200 OK 48 | Content-Type: text/turtle; charset=utf-8 49 | 50 | ``` 51 | 52 | ## put__named_graph 53 | 54 | ### Request 55 | 56 | ``` 57 | PUT $GRAPHSTORE$/foo HTTP/1.1 58 | Host: $HOST$ 59 | Content-Type: text/turtle; charset=utf-8 60 | 61 | @prefix foaf: . 62 | @prefix v: . 63 | 64 | a foaf:Person; 65 | foaf:businessCard [ 66 | a v:VCard; 67 | v:fn "John Doe" 68 | ]. 69 | ``` 70 | 71 | ### Response 72 | 73 | ``` 74 | 201 Created 75 | 76 | ``` 77 | 78 | ## get_of_put__named_graph 79 | 80 | ### Request 81 | 82 | ``` 83 | GET $GRAPHSTORE$/foo HTTP/1.1 84 | Host: $HOST$ 85 | Accept: text/turtle 86 | 87 | ``` 88 | ### Response 89 | 90 | ``` 91 | 200 OK 92 | Content-Type: text/turtle; charset=utf-8 93 | 94 | @prefix foaf: . 95 | @prefix v: . 96 | 97 | a foaf:Person; 98 | foaf:businessCard [ 99 | a v:VCard; 100 | v:fn "John Doe" 101 | ]. 102 | ``` 103 | 104 | ## get_of_put__default_graph 105 | 106 | ### Request 107 | 108 | ``` 109 | GET $GRAPHSTORE$?default HTTP/1.1 110 | Host: $HOST$ 111 | Accept: text/turtle 112 | 113 | ``` 114 | 115 | ### Response 116 | 117 | ``` 118 | 200 OK 119 | Content-Type: text/turtle; charset=utf-8 120 | 121 | @prefix foaf: . 122 | @prefix v: . 123 | 124 | a foaf:Person; 125 | foaf:businessCard [ 126 | a v:VCard; 127 | v:fn "John Doe" 128 | ]. 129 | ``` 130 | 131 | -------------------------------------------------------------------------------- /test/endpoint_protocol/test_graphstore_with_graph.md: -------------------------------------------------------------------------------- 1 | # Graph Store Protocol Tests [rdflib.Graph()] 2 | 3 | Testing the graph store protocol implementation backed by a plain 4 | Graph. The graph is served as default graph and no other graphs can be 5 | created. 6 | 7 | ## put__initial_state 8 | 9 | ### Request 10 | 11 | ``` 12 | PUT $GRAPHSTORE$?default HTTP/1.1 13 | Host: $HOST$ 14 | Content-Type: text/turtle; charset=utf-8 15 | 16 | @prefix foaf: . 17 | @prefix v: . 18 | 19 | a foaf:Person; 20 | foaf:businessCard [ 21 | a v:VCard; 22 | v:fn "John Doe" 23 | ]. 24 | ``` 25 | 26 | ### Response 27 | 28 | ``` 29 | 204 No Content 30 | 31 | ``` 32 | 33 | ## get_of_put__initial_state 34 | 35 | ### Request 36 | 37 | ``` 38 | GET $GRAPHSTORE$?default HTTP/1.1 39 | Host: $HOST$ 40 | Accept: text/turtle 41 | 42 | ``` 43 | 44 | ### Response 45 | 46 | ``` 47 | 200 OK 48 | Content-Type: text/turtle; charset=utf-8 49 | 50 | @prefix foaf: . 51 | @prefix v: . 52 | 53 | a foaf:Person; 54 | foaf:businessCard [ 55 | a v:VCard; 56 | v:fn "John Doe" 57 | ]. 58 | ``` 59 | 60 | ## get__named_graph 61 | 62 | ### Request 63 | 64 | ``` 65 | GET $GRAPHSTORE$/foo HTTP/1.1 66 | Host: $HOST$ 67 | Accept: text/turtle 68 | 69 | ``` 70 | 71 | ### Response 72 | 73 | ``` 74 | 404 Not Found 75 | 76 | ``` 77 | 78 | ## put__named_graph 79 | 80 | ### Request 81 | 82 | ``` 83 | PUT $GRAPHSTORE$/foo HTTP/1.1 84 | Host: $HOST$ 85 | Content-Type: text/turtle; charset=utf-8 86 | 87 | @prefix foaf: . 88 | @prefix v: . 89 | 90 | a foaf:Person; 91 | foaf:businessCard [ 92 | a v:VCard; 93 | v:fn "John Doe" 94 | ]. 95 | ``` 96 | 97 | ### Response 98 | 99 | ``` 100 | 400 Named graphs not supported 101 | 102 | ``` 103 | 104 | ## post__named_graph 105 | 106 | ### Request 107 | 108 | ``` 109 | POST $GRAPHSTORE$/foo HTTP/1.1 110 | Host: $HOST$ 111 | Content-Type: text/turtle; charset=utf-8 112 | 113 | @prefix foaf: . 114 | @prefix v: . 115 | 116 | a foaf:Person; 117 | foaf:businessCard [ 118 | a v:VCard; 119 | v:fn "John Doe" 120 | ]. 121 | ``` 122 | 123 | ### Response 124 | 125 | ``` 126 | 400 Named graphs not supported 127 | 128 | ``` 129 | 130 | ## delete__default_graph 131 | 132 | ### Request 133 | 134 | ``` 135 | DELETE $GRAPHSTORE$?default HTTP/1.1 136 | Host: $HOST$ 137 | ``` 138 | 139 | ### Response 140 | 141 | ``` 142 | 204 No Content 143 | ``` 144 | 145 | ## get_of_delete__default_graph 146 | 147 | ### Request 148 | 149 | ``` 150 | GET $GRAPHSTORE$?default HTTP/1.1 151 | Host: $HOST$ 152 | Accept: text/turtle 153 | 154 | ``` 155 | 156 | ### Response 157 | 158 | ``` 159 | 200 OK 160 | Content-Type: text/turtle; charset=utf-8 161 | 162 | ``` 163 | 164 | -------------------------------------------------------------------------------- /test/test_endpoint_protocol.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import logging 3 | import subprocess 4 | import threading 5 | import os 6 | import re 7 | import urllib 8 | import httplib 9 | import rdflib 10 | import rdflib.compare 11 | import rdflib_web.endpoint 12 | import fileinput 13 | 14 | class _TestSyntaxError(Exception): 15 | def __init__(self, filename, lineno, error): 16 | super(Exception, self).__init__( 17 | "Error in test case %s at line %s: %s" % (filename, lineno, error) 18 | ) 19 | 20 | 21 | class Parser(): 22 | r_title = re.compile(r"^#(?!#)\s*(.*)") 23 | r_test = re.compile("^##(?!#)\s*(.*)") 24 | r_request = re.compile("^###\s*request", re.I) 25 | r_response = re.compile("^###\s*response", re.I) 26 | r_empty = re.compile("^\s*$") 27 | r_indent = re.compile("^(\s+)") 28 | 29 | def error(self, message): 30 | raise _TestSyntaxError(self.f.filename(), self.f.filelineno(), message) 31 | 32 | def parse(self, filename): 33 | self.f = fileinput.input((filename,)) 34 | self.tests = [] 35 | self.state = self.title 36 | 37 | for l in self.f: 38 | self.state(l) 39 | 40 | if not self.tests: 41 | self.error("No tests found") 42 | elif not self.tests[-1]['request']: 43 | self.error("Expected '### Request'") 44 | elif not self.tests[-1]['response']: 45 | self.error("Expected '### Response'") 46 | 47 | return self.title, self.tests 48 | 49 | def title(self, l): 50 | m = self.r_title.match(l) 51 | if m: 52 | self.title = m.group(1) 53 | self.state = self.test 54 | else: 55 | self.error("First line must be a title") 56 | 57 | def test(self, l): 58 | if l.startswith('#'): 59 | m = self.r_test.match(l) 60 | if not m: 61 | self.error("Expected '##'") 62 | test = {'name': m.group(1), 'request': "", 'response': ""} 63 | self.tests.append(test) 64 | self.state = self.request 65 | 66 | def request(self, l): 67 | if l.startswith('#'): 68 | m = self.r_request.match(l) 69 | if not m: 70 | self.error("Expected '### Request'") 71 | self.state = lambda l: self.block(l, 'request', self.response) 72 | 73 | def response(self, l): 74 | if l.startswith('#'): 75 | m = self.r_response.match(l) 76 | if not m: 77 | self.error("Expected '### Response'") 78 | self.state = lambda l: self.block(l, 'response', self.test) 79 | 80 | def block(self, l, field, next_state): 81 | if l.startswith('#'): 82 | self.error("Expected code block") 83 | elif l.rstrip() == '```': 84 | self.state = lambda l: self.consume_block(l, field, next_state) 85 | 86 | def consume_block(self, l, field, next_state): 87 | if l.rstrip().startswith('```'): 88 | self.state = next_state 89 | else: 90 | self.tests[-1][field] += l 91 | 92 | def _test_function_factory(filename): 93 | def test_function(self): 94 | self.from_file(filename) 95 | return test_function 96 | 97 | def _discover_tests(directory): 98 | # Name of this function begins with an underscore because 99 | # otherwise unittest thinks it is a test. 100 | tests = filter(lambda name: name.startswith("test_"), os.listdir(directory)) 101 | def decorator(cls): 102 | for t in tests: 103 | test_function = _test_function_factory("%s/%s" % (directory, t)) 104 | setattr(cls, t.split('.')[0], test_function) 105 | return cls 106 | return decorator 107 | 108 | @_discover_tests("test/endpoint_protocol") 109 | class TestEndpointProtocol(unittest.TestCase): 110 | port = 57234 111 | host = 'localhost' 112 | graphstore_path = '/graph-store'.format(host, port) 113 | tests = [] 114 | connection = None 115 | app = None 116 | 117 | # Blarg! Using shared fixture, because I don't know how to stop a 118 | # running flask app in a thread. Running the app in a 119 | # subprocess or using app.test_client() should be considered. 120 | @classmethod 121 | def setUpClass(cls): 122 | ds=rdflib.Dataset() 123 | cls.app=rdflib_web.endpoint.get(ds) 124 | t=threading.Thread(target=lambda : cls.app.run(port=cls.port)) 125 | t.daemon=True 126 | t.start() 127 | import time 128 | time.sleep(1) 129 | cls.connection = httplib.HTTPConnection(cls.host, cls.port, timeout=5) 130 | 131 | def replace_constants(self, s): 132 | s = s.replace('$GRAPHSTORE$', self.graphstore_path) 133 | return s.replace('$HOST$', self.host+':'+str(self.port)) 134 | 135 | def replace_variables(self, s): 136 | if self.newpath: 137 | # The host part must be removed, because it should not 138 | # turn up in the first request line (flask chokes at it, 139 | # although HTTP explicitely allows it). 140 | newpath = re.sub(r"^http://[^:]+:[^/]+", '', self.newpath) 141 | s = s.replace('$NEWPATH$', newpath) 142 | else: 143 | s = s.replace('$NEWPATH$', 'NEW_PATH_MISSING') 144 | return s 145 | 146 | def readProtocol(self, data): 147 | lines = iter(data.split('\n')) 148 | firstline = next(lines) 149 | headers = dict() 150 | for l in lines: 151 | if l.isspace() or not l: break 152 | k, v = l.split(': ', 1) 153 | if k.lower() not in ['content-length','host']: 154 | headers[k.lower()] = v 155 | body = '\n'.join(lines) 156 | return firstline, headers, body 157 | 158 | # The compare functions return a list of strings, each describing 159 | # a difference. If the list is empty, no difference has been 160 | # found. 161 | def compareStatus(self, expected, received): 162 | if not expected == received: 163 | return ["Status is {}, but should be {}".format(received, expected)] 164 | else: 165 | return [] 166 | 167 | def compareHeaders(self, expected, received): 168 | result = [] 169 | for k, v in expected.items(): 170 | if not k in received: 171 | result.append("Header {} not present in response".format(k)) 172 | else: 173 | # Create a regular expression matching the header and 174 | # reading out $NEWPATH$ 175 | v_re = re.sub(r'([^\w\s])', r'\\\1', v) 176 | v_re = re.sub(r'\\\$NEWPATH\\\$', r'(.*)', v_re) # $NEWPATH$ escaped by previous line 177 | v_re = '^' + v_re + '$' 178 | m = re.match(v_re, received[k]) 179 | if m and m.groups() and m.group(1): 180 | self.newpath = m.group(1) 181 | elif not m: 182 | result.append("Header {} has value {} instead of {}".format(k, received[k], v)) 183 | return result 184 | 185 | def compareBody(self, expected, received, content_type): 186 | mimetype = content_type.split(';', 1)[0] 187 | if mimetype == 'text/turtle': 188 | g_expected = rdflib.compare.IsomorphicGraph() 189 | g_expected.parse(data=expected, format=mimetype) 190 | g_received = rdflib.compare.IsomorphicGraph() 191 | g_received.parse(data=received, format=mimetype) 192 | _, in_expected, in_received = rdflib.compare.graph_diff(g_expected, g_received) 193 | if in_expected or in_received: 194 | msg = "Triples expected but not returned:\n" 195 | for t in sorted(in_expected): 196 | msg += " " + " ".join([n.n3() for n in t]) + " .\n" 197 | msg += "Triples returned but not expected:\n" 198 | for t in sorted(in_received): 199 | msg+=" " + " ".join([n.n3() for n in t]) + " .\n" 200 | return [msg] 201 | elif not expected == received: 202 | return ["Body is:\n{}but should be\n{}".format(received, expected)] 203 | return [] 204 | 205 | def runtest(self, name, request, response): 206 | logging.info("Prepairing test {}".format(name)) 207 | request = self.replace_constants(request) 208 | request = self.replace_variables(request) 209 | response = self.replace_constants(response) 210 | # Read request into data structure 211 | firstline, request_headers, request_body = self.readProtocol(request) 212 | match = re.match("^(\w+) ([^ ]+)", firstline) 213 | request_method = match.group(1) 214 | request_url = match.group(2) 215 | # Read response into data structure 216 | firstline, expected_headers, expected_body = self.readProtocol(response) 217 | expected_status = int(firstline.split(' ', 1)[0]) 218 | 219 | logging.info("Running test {}".format(name)) 220 | self.connection.request(request_method, request_url, request_body, request_headers) 221 | # Get response 222 | response = self.connection.getresponse(); 223 | response_status = response.status; 224 | response_headers = {k.lower(): v for k, v in response.getheaders()} 225 | response_body = response.read() 226 | # Collect errors 227 | errors = [] 228 | # Compare response status 229 | errors += self.compareStatus(expected_status, response_status) 230 | # Compare response headers 231 | errors += self.compareHeaders(expected_headers, response_headers) 232 | # Compare response body only on success 233 | if response_status < 400: 234 | errors += self.compareBody(expected_body, response_body, response_headers.get('content-type')) 235 | # Report 236 | if errors: 237 | logging.info("=== FAILURE === Test {} failed: ".format(name)) 238 | logging.info("\n".join(errors)) 239 | return False 240 | else: 241 | logging.info("Test {} passed".format(name)) 242 | return True 243 | 244 | def from_file(self, filename): 245 | parser = Parser() 246 | title, tests = parser.parse(filename) 247 | 248 | # Because of the shared fixture, mess with the endpoint's 249 | # internal in order to reset the dataset 250 | m = re.search(r"\[(.*)\]", title) 251 | initExpression = m.group(1) if m else "rdflib.Dataset()" 252 | logging.info("Initializing endpoint with `%s`" % initExpression) 253 | self.app.config["ds"] = eval(initExpression) 254 | if "generic" in self.app.config: 255 | self.app.config["generic"].ds = self.app.config["ds"] 256 | self.newpath = None 257 | 258 | failed_tests = [t['name'] for t in tests if not self.runtest(**t)] 259 | 260 | if failed_tests: 261 | self.fail("These tests failed: {}\nNOTE: Some tests may have failed because they depend on the success of preceding ones!".format(", ".join(failed_tests))) 262 | 263 | -------------------------------------------------------------------------------- /test/test_lod_app.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | from httplib import HTTPConnection 4 | 5 | import rdflib 6 | import rdflib_web.lod as lod 7 | from rdflib_web.bookdb import bookdb 8 | 9 | 10 | 11 | def test_lod_app(): 12 | 13 | def req(u, ok=200): 14 | h.request('GET', u) 15 | r = h.getresponse() 16 | assert r.status == ok, 'Request for %s did not succeed, status code: %d'%(u,r.status) 17 | 18 | app = lod.get(bookdb) 19 | t=threading.Thread(target=lambda : app.run(port=57234)) 20 | t.daemon=True 21 | t.start() 22 | 23 | time.sleep(2) # give the web-app time to start 24 | 25 | h = HTTPConnection('localhost', 57234) 26 | 27 | req('/') 28 | req('/instances') 29 | # req('/resource/Book/book1') # httplib does not follow redirects 30 | req('/page/Book/book1') 31 | req('/data/Book/book1.nt') 32 | req('/rdfgraph/Book/book1.png') 33 | req('/page/Class/Class') 34 | req('/page/Class/Property') 35 | req('/picked/') 36 | req('/download/nt') 37 | 38 | req('/pick?uri=http%3A%2F%2Fexample.org%2Fbook%2Fbook1', 302) 39 | req('/picked/download/nt') 40 | req('/picked/rdfgraph/png') 41 | -------------------------------------------------------------------------------- /test/test_served_sparql_store.py: -------------------------------------------------------------------------------- 1 | #from nose.exc import SkipTest 2 | #raise SkipTest("SPARQL store server test skipped") 3 | import unittest 4 | import threading 5 | import rdflib 6 | import rdflib_web.endpoint 7 | 8 | 9 | class TestSPARQLStore(unittest.TestCase): 10 | def testSPARQLStore(self): 11 | g=rdflib.Graph(identifier='http://example.org/testgraph') 12 | 13 | data=""" "SPARQL Tutorial" . 14 | "Moose bite can be very n\xc3\xb6sty."@se . 15 | 16 | """ 17 | 18 | g.parse(data=data, format='n3') 19 | 20 | # # create our own SPARQL endpoint 21 | 22 | app=rdflib_web.endpoint.get(g) 23 | t=threading.Thread(target=lambda : app.run(port=57234)) 24 | t.daemon=True 25 | t.start() 26 | 27 | import time 28 | time.sleep(1) 29 | 30 | g2=rdflib.ConjunctiveGraph('SPARQLStore') 31 | g2.open("http://localhost:57234/sparql") 32 | b=rdflib.URIRef("http://example.org/book/book1") 33 | b2=rdflib.URIRef("http://example.org/book/b\xc3\xb6\xc3\xb6k8") 34 | DCtitle=rdflib.URIRef("http://purl.org/dc/elements/1.1/title") 35 | self.assertEqual(len(list(g2.triples((b,None,None)))), 1) 36 | self.assertEqual(list(g2.objects(b,DCtitle))[0], rdflib.Literal("SPARQL Tutorial")) 37 | 38 | self.assertEqual(list(g2.objects(b2,DCtitle))[0], list(g.objects(b2,DCtitle))[0]) 39 | --------------------------------------------------------------------------------