Skip to content

Commit

Permalink
Add HTTP API based on FastAPI
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Jun 2, 2020
1 parent 5ad76c0 commit 140fef3
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -3,3 +3,4 @@
apicast_sites.txt
*.pyc
*.egg-info
/dist
5 changes: 5 additions & 0 deletions CHANGES.rst
Expand Up @@ -7,6 +7,11 @@ Development
===========


2020-06-02 0.3.0
================
- Add HTTP API based on FastAPI


2020-06-02 0.2.0
================
- Convert to Python3
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Expand Up @@ -30,6 +30,10 @@ Acquire information for given site slug ``berlin_brandenburg/potsdam``, output a

apicast beeflight forecast --station=berlin_brandenburg/potsdam --format=table

Start HTTP API service::

apicast service


*******
Example
Expand Down
82 changes: 82 additions & 0 deletions apicast/api.py
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# (c) 2020 Andreas Motl <andreas@terkin.org>
# License: GNU Affero General Public License, Version 3
import logging
from fastapi import FastAPI, Query
from fastapi.responses import HTMLResponse

from apicast import __appname__, __version__
from apicast.core import dwd_beeflight_forecast_stations, dwd_beeflight_forecast_stations_site_slugs, \
dwd_beeflight_site_url_by_slug, grok_beeflight_forecast

app = FastAPI()

log = logging.getLogger(__name__)


@app.get("/", response_class=HTMLResponse)
def index():
appname = f'{__appname__} {__version__}'
about = 'Apicast acquires bee flight forecast information published by Deutscher Wetterdienst (DWD).'
return f"""
<html>
<head>
<title>{appname}</title>
</head>
<body>
{about}
<hr/>
Index
<ul>
<li><a href="beeflight/stations">List of federal states / sites</a></li>
<li><a href="beeflight/stations/site-slugs">List of site slugs</a></li>
</ul>
<hr/>
Examples
<ul>
<li><a href="beeflight/forecast/berlin_brandenburg/potsdam">Bee flight forecast for "berlin_brandenburg/potsdam"</a></li>
<li><a href="beeflight/forecast/bayern/regensburg">Bee flight forecast for "bayern/regensburg"</a></li>
</ul>
</body>
</html>
"""


@app.get("/beeflight/stations")
def beeflight_stations():
stations = dwd_beeflight_forecast_stations()
return stations


@app.get("/beeflight/stations/site-slugs")
def beeflight_stations_site_slugs():
slugs = dwd_beeflight_forecast_stations_site_slugs()
return slugs


@app.get("/beeflight/forecast/{state}/{site}")
def beeflight_forecast_by_slug(state: str, site: str):
station_slug = f"{state}/{site}"

try:
url = dwd_beeflight_site_url_by_slug(station_slug)
result = grok_beeflight_forecast(url)
data = result['data']
if not data:
raise ValueError('No data found or unable to parse')
except Exception as ex:
return {'error': str(ex)}

result = []
for item in data[1:]:
item = dict(zip(data[0], item))
result.append(item)

return result


def start_service(listen_address):
host, port = listen_address.split(':')
port = int(port)
from uvicorn.main import run
run(app='apicast.api:app', host=host, port=port, reload=True)
26 changes: 18 additions & 8 deletions apicast/cli.py
Expand Up @@ -7,7 +7,8 @@
from docopt import docopt, DocoptExit

from apicast import __appname__, __version__
from apicast.core import grok_beeflight_forecast, dwd_beeflight_forecast_stations, dwd_beeflight_site_url_by_slug
from apicast.core import grok_beeflight_forecast, dwd_beeflight_forecast_stations, dwd_beeflight_site_url_by_slug, \
dwd_beeflight_forecast_stations_site_slugs
from apicast.util import normalize_options, setup_logging

log = logging.getLogger(__name__)
Expand All @@ -21,13 +22,15 @@ def run():
apicast beeflight stations [--site-slugs]
apicast beeflight forecast --url=<url> [--format=<format>]
apicast beeflight forecast --station=<station> [--format=<format>]
apicast service [--listen=<listen>]
apicast --version
apicast (-h | --help)
Options:
--url=<url> URL to detail page
--station=<station> Station identifier
--format=<format> Output format: "json" or "table". Default: json
--listen=<listen> HTTP server listen address. [Default: localhost:24640]
--version Show version information
--debug Enable debug messages
-h --help Show this screen
Expand Down Expand Up @@ -57,18 +60,25 @@ def run():
# Debugging
log.debug('Options: {}'.format(json.dumps(options, indent=4)))

# Run service.
if options.service:
listen_address = options.listen
log.info(f'Starting {name}')
log.info(f'Starting web service on {listen_address}')
from apicast.api import start_service
start_service(listen_address)
return

# Run command.
if options.stations:
stations = dwd_beeflight_forecast_stations()

if options.site_slugs:
slugs = []
for station in stations:
for site in station.sites:
slugs.append(site.slug)
print(json.dumps(slugs, indent=4))
result = dwd_beeflight_forecast_stations_site_slugs()

else:
print(json.dumps(stations, indent=4))
result = dwd_beeflight_forecast_stations()

print(json.dumps(result, indent=4))

# Fetch and extract forecast information.
elif options.url:
Expand Down
11 changes: 11 additions & 0 deletions apicast/core.py
Expand Up @@ -75,6 +75,15 @@ def dwd_beeflight_forecast_stations():
return results


def dwd_beeflight_forecast_stations_site_slugs():
stations = dwd_beeflight_forecast_stations()
slugs = []
for station in stations:
for site in station.sites:
slugs.append(site.slug)
return slugs


def dwd_beeflight_site_url_by_slug(slug):
url = f"https://www.dwd.de/DE/fachnutzer/freizeitgaertner/1_gartenwetter/{slug}/_node.html"
return url
Expand All @@ -88,6 +97,8 @@ def grok_beeflight_forecast(url):

# Find content section and extract elements.
subject = page.soup.find(string=u'Prognose des Bienenfluges')
if not subject:
raise ValueError('No forecast available for this station')
station = subject.find_next('br').next.strip()
table = subject.parent.find_next_sibling('table')
# TODO: Read table footer "© Deutscher Wetterdienst, erstellt 12.04.2018 04:00 UTC"
Expand Down
6 changes: 6 additions & 0 deletions setup.py
Expand Up @@ -60,5 +60,11 @@
"docopt==0.6.2",
"munch==2.5.0",
],
extras_require={
'service': [
'fastapi==0.55.1',
'uvicorn==0.11.5',
],
},
entry_points={"console_scripts": ["apicast = apicast.cli:run"]},
)

0 comments on commit 140fef3

Please sign in to comment.