Skip to content

Commit

Permalink
Merge pull request #172 from Materials-Consortia/ml-evs/add_landing_page
Browse files Browse the repository at this point in the history
Add human-readable landing page at base URLs.
New dependency: `Jinja2`.
  • Loading branch information
CasperWA committed Feb 10, 2020
2 parents 44b1b9b + 2ec6c7f commit ee15c4a
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 16 deletions.
1 change: 1 addition & 0 deletions .github/workflows/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ pymongo==3.10.1
mongomock==3.19.0
django==2.2.10
elasticsearch-dsl==6.4.0
Jinja2==2.11.1
1 change: 1 addition & 0 deletions .github/workflows/requirements_eager.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pymongo
mongomock
django
elasticsearch_dsl
Jinja2
4 changes: 2 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The paths must be relative from your current working directory, where your `serv
The index meta-database is set up to populate a `mongomock` in-memory database with resources from a static `json` file containing the `child` resources you, as a database provider, want to serve under this index meta-database.

Running the index meta-database is then as simple as writing `./run.sh index` in a terminal from the root of this package.
You can find it at the base URL: [`http://localhost:5001/index/optimade/`](http://localhost:5001/index/optimade/).
You can find it at the base URL: [`http://localhost:5001/optimade`](http://localhost:5001/optimade).

_Note_: `server.cfg` is loaded from the current working directory, from where you run `run.sh`.
E.g., if you have installed `optimade` on a Linux machine at `/home/USERNAME/optimade/optimade-python-tools` and you run the following:
Expand Down Expand Up @@ -73,4 +73,4 @@ Running the following:
uvicorn optimade.server.main_index:app --reload --port 5001
```

will run the index meta-database server at <http://localhost:5001/index/optimade>.
will run the index meta-database server at <http://localhost:5001/optimade>.
2 changes: 1 addition & 1 deletion optimade/server/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ prefix = _exmpl_
name = Example provider
description = Provider used for examples, not to be assigned to a real database
homepage = http://example.com
index_base_url = http://localhost:5001/index/optimade
index_base_url = http://localhost:5001/optimade

[structures]
band_gap :
Expand Down
2 changes: 1 addition & 1 deletion optimade/server/data/test_links.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"type": "parent",
"name": "Index meta-database",
"description": "Index for example's OPTiMaDe databases",
"base_url": "http://localhost:5001/index/optimade",
"base_url": "http://localhost:5001/optimade",
"homepage": "https://example.com"
}
]
8 changes: 7 additions & 1 deletion optimade/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .entry_collections import MongoCollection
from .config import CONFIG
from .middleware import RedirectSlashedURLs
from .routers import info, links, references, structures
from .routers import info, links, references, structures, landing
from .routers.utils import get_providers, BASE_URL_PREFIXES

from optimade import __api_version__, __version__
Expand Down Expand Up @@ -83,6 +83,11 @@ def load_entries(endpoint_name: str, endpoint_collection: MongoCollection):
app.include_router(structures.router, prefix=BASE_URL_PREFIXES["major"])


# Add the router for the landing page at `/optimade` and for all prefixes
app.include_router(landing.router, prefix="/optimade")
app.include_router(landing.router, prefix=BASE_URL_PREFIXES["major"])


def add_optional_versioned_base_urls(app: FastAPI):
"""Add the following OPTIONAL prefixes/base URLs to server:
```
Expand All @@ -95,6 +100,7 @@ def add_optional_versioned_base_urls(app: FastAPI):
app.include_router(links.router, prefix=BASE_URL_PREFIXES[version])
app.include_router(references.router, prefix=BASE_URL_PREFIXES[version])
app.include_router(structures.router, prefix=BASE_URL_PREFIXES[version])
app.include_router(landing.router, prefix=BASE_URL_PREFIXES[version])


def update_schema(app: FastAPI):
Expand Down
17 changes: 10 additions & 7 deletions optimade/server/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@
from starlette.requests import Request
from starlette.responses import RedirectResponse

from .routers import ENTRY_COLLECTIONS


class RedirectSlashedURLs(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
"""Redirect URL requests ending with a slash to non-slashed URLs
E.g., `http://example.org/info/` -> `http://example.org/info`
E.g., `http://example.org/optimade/v0/info/` -> `http://example.org/optimade/v0/info`
"""
if request.scope["path"].endswith("/"):
if request.scope["path"].endswith("/") and any(
request.scope["path"].endswith(f"{endpoint}/")
for endpoint in list(ENTRY_COLLECTIONS.keys()) + ["info"]
):
redirect_scope = dict(request.scope)
redirect_scope["path"] = redirect_scope["path"][:-1]
redirect_url = URL(scope=redirect_scope)
return RedirectResponse(url=str(redirect_url))

# Make sure we're not dealing with a URL path (after the domain) of `/`
if redirect_scope["path"] != "/":
redirect_scope["path"] = redirect_scope["path"][:-1]
redirect_url = URL(scope=redirect_scope)
return RedirectResponse(url=str(redirect_url))
response = await call_next(request)
return response
40 changes: 40 additions & 0 deletions optimade/server/routers/landing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
""" OPTiMaDe landing page, rendered as a Jinja2 template. """

from starlette.routing import Router, Route
from optimade import __api_version__
from pathlib import Path
from starlette.templating import Jinja2Templates

from . import ENTRY_COLLECTIONS
from .utils import meta_values

template_dir = Path(__file__).parent.joinpath("static").resolve()
TEMPLATES = Jinja2Templates(directory=[template_dir])


async def landing(request):
""" Show a human-readable landing page when the base URL is accessed. """

meta = meta_values(str(request.url), 1, 1, more_data_available=False)

major_version = __api_version__.split(".")[0]
versioned_url = (
f"{request.url}"
if f"v{major_version}" in f"{request.url.path}"
else f"{request.url}v{major_version}/"
)

context = {
"request": request,
"request_url": request.url,
"api_version": __api_version__,
"implementation": meta.implementation,
"versioned_url": versioned_url,
"provider": meta.provider,
"endpoints": list(ENTRY_COLLECTIONS.keys()) + ["info"],
}

return TEMPLATES.TemplateResponse("landing_page.html", context)


router = Router(routes=[Route("/", endpoint=landing)])
49 changes: 49 additions & 0 deletions optimade/server/routers/static/landing_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<title>{{ optimade_title }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="optimade,materials,crystals,database">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
{% endblock %}

<style>
html { margin: 2em; }
h4, p { margin-left: 2em; }
span.version {background-color: lightgrey; font-weight: bold; font-family: monospace; padding: 0em 0.5em 0em 0.5em; border-radius: 1em;}
</style>
</head>
<body>
<img id="logo" width="100" src="https://avatars0.githubusercontent.com/u/23107754?s=400&u=e1580af496ac195a3ac4c445a66a5699efb0c3d3"></img>
<h3>This is an <a href="https://www.optimade.org">OPTiMaDe</a> base URL which can be queried with an OPTiMaDe client.</h3>
<h3>OPTiMaDe version:</h3>
<h3><span class="version">{{ api_version }}</span></h3>
<h3>Provider:</h3>
<h4>{{ provider.name }}</h4>
<p> Prefix: <span class="version">{{ provider.prefix }}</span></p>
<p> {{ provider.description }} </p>
<p><a href={{provider.homepage}}>{{ provider.homepage }}</a></p>
<h3>Implementation:</h3>
<h4>{{ implementation.name }}</h4>
<p>Version: <span class="version">{{ implementation.version }}</span></p>
<p><a href={{ implementation.source_url }}>{{ implementation.source_url }}</a></p>
<h3>Available endpoints:</h3>
<ul>
{% for endpoint in endpoints %}
{{ '<li><a href="{}{}">{}{}</a></li>'.format(versioned_url, endpoint, versioned_url, endpoint) | safe }}
{% endfor %}
</ul>
<h3>Index base URL:</h3>
<p><a href={{ provider.index_base_url }}>{{ provider.index_base_url }}</a></p>
</body>
<footer>
<div class="footer_text">
<ul style="list-style-type: none; margin: 0; padding:0; overflow: hidden">
<li style="padding-top: 5px;">Compliant with the <i class="fa fa-github"></i> <a href="https://github.com/Materials-Consortia/OPTiMaDe">OPTiMaDe specification</a>.</li>
<li style="padding-top: 5px;">Powered by <i class="fa fa-github"></i> <a href="https://www.github.com/Materials-Consortia/optimade-python-tools">Materials-Consortia/optimade-python-tools</a></li>
</ul>
</div>
</footer>
</html>
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

# Dependencies
mongo_deps = ["pymongo~=3.10", "mongomock~=3.19"]
server_deps = ["uvicorn"] + mongo_deps
server_deps = ["uvicorn", "Jinja2~=2.11"] + mongo_deps
django_deps = ["django~=2.2,>=2.2.9"]
elastic_deps = ["elasticsearch-dsl~=6.4"]
testing_deps = [
Expand Down
2 changes: 1 addition & 1 deletion tests/server/config_test.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ prefix = _exmpl_
name = Example provider
description = Provider used for examples, not to be assigned to a real database
homepage = http://example.com
index_base_url = http://localhost:5001/index/optimade
index_base_url = http://localhost:5001/optimade

[structures]
band_gap :
Expand Down
2 changes: 1 addition & 1 deletion tests/server/config_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"name": "Example provider",
"description": "Provider used for examples, not to be assigned to a real database",
"homepage": "http://example.com",
"index_base_url": "http://localhost:5001/index/optimade"
"index_base_url": "http://localhost:5001/optimade"
},
"provider_fields": {
"structures": [
Expand Down
2 changes: 1 addition & 1 deletion tests/server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def get_index_client() -> TestClient:
app.include_router(links.router)
# need to explicitly set base_url, as the default "http://testserver"
# does not validate as pydantic UrlStr model
return TestClient(app, base_url="http://example.org/index/optimade/v0")
return TestClient(app, base_url="http://example.org/optimade/v0")


class SetClient(abc.ABC):
Expand Down

0 comments on commit ee15c4a

Please sign in to comment.