diff --git a/.gitignore b/.gitignore index fa16fb7..5b4880f 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,6 @@ local.openapi.yml # misc *.swp .pytest_cache + +#Eclipse project +.project diff --git a/.travis.yml b/.travis.yml index a9c71fc..b1141bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ sudo: false env: global: - - PYGEOAPI_CONFIG=pygeoapi-config.yml + - PYGEOAPI_CONFIG="$(pwd)/pygeoapi-config.yml" - CPLUS_INCLUDE_PATH=/usr/include/gdal - C_INCLUDE_PATH=/usr/include/gdal @@ -47,9 +47,11 @@ before_script: script: - pytest --cov=pygeoapi + - make -C ./docs html - find . -type f -name "*.py" | xargs flake8 - python3 setup.py --long-description | rst2html5.py + after_success: - python3 setup.py sdist bdist_wheel --universal - debuild -b -uc -us diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..3bf255e --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = pygeoapi +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/source/_static/intro_page_pygeoapi.png b/docs/source/_static/intro_page_pygeoapi.png new file mode 100644 index 0000000..855c542 Binary files /dev/null and b/docs/source/_static/intro_page_pygeoapi.png differ diff --git a/docs/source/_static/openapi_get_collection.png b/docs/source/_static/openapi_get_collection.png new file mode 100644 index 0000000..c22c9f9 Binary files /dev/null and b/docs/source/_static/openapi_get_collection.png differ diff --git a/docs/source/_static/openapi_get_collection_result.png b/docs/source/_static/openapi_get_collection_result.png new file mode 100644 index 0000000..4fb93eb Binary files /dev/null and b/docs/source/_static/openapi_get_collection_result.png differ diff --git a/docs/source/_static/openapi_get_collections.png b/docs/source/_static/openapi_get_collections.png new file mode 100644 index 0000000..a46dbec Binary files /dev/null and b/docs/source/_static/openapi_get_collections.png differ diff --git a/docs/source/_static/openapi_get_collections_result.png b/docs/source/_static/openapi_get_collections_result.png new file mode 100644 index 0000000..ca7fbc0 Binary files /dev/null and b/docs/source/_static/openapi_get_collections_result.png differ diff --git a/docs/source/_static/openapi_get_item.png b/docs/source/_static/openapi_get_item.png new file mode 100644 index 0000000..ed6c344 Binary files /dev/null and b/docs/source/_static/openapi_get_item.png differ diff --git a/docs/source/_static/openapi_get_item_id.png b/docs/source/_static/openapi_get_item_id.png new file mode 100644 index 0000000..d88b187 Binary files /dev/null and b/docs/source/_static/openapi_get_item_id.png differ diff --git a/docs/source/_static/openapi_get_item_id2.png b/docs/source/_static/openapi_get_item_id2.png new file mode 100644 index 0000000..bfb3ed8 Binary files /dev/null and b/docs/source/_static/openapi_get_item_id2.png differ diff --git a/docs/source/_static/openapi_intro_page.png b/docs/source/_static/openapi_intro_page.png new file mode 100644 index 0000000..38938c4 Binary files /dev/null and b/docs/source/_static/openapi_intro_page.png differ diff --git a/docs/source/code.rst b/docs/source/code.rst new file mode 100644 index 0000000..d5bba38 --- /dev/null +++ b/docs/source/code.rst @@ -0,0 +1,219 @@ +.. _code: + + + +Code documentation +================== + +Top level code documentation. Follow link in section for module/class member information + + +API +--- + + +.. automodule:: pygeoapi.api + :members: + :private-members: + :special-members: + + + +flask_app +--------- + +.. automodule:: pygeoapi.flask_app + :show-inheritance: + :members: + :private-members: + :special-members: + + + +Logging +------- + + +.. automodule:: pygeoapi.log + :show-inheritance: + :members: + :private-members: + :special-members: + + + +OpenAPI +------- + +.. automodule:: pygeoapi.openapi + :show-inheritance: + :members: + :private-members: + :special-members: + + +Plugins +------- + +.. note:: Please consult section :ref:`plugins` + + +.. automodule:: pygeoapi.plugin + :show-inheritance: + :members: + :private-members: + :special-members: + +Utils +----- + +.. automodule:: pygeoapi.util + :show-inheritance: + :members: + :private-members: + :special-members: + + +Formatter package +----------------- + + +.. automodule:: pygeoapi.formatter + :show-inheritance: + :members: + :private-members: + :special-members: + +Base class +^^^^^^^^^^ +.. automodule:: pygeoapi.formatter.base + :show-inheritance: + :members: + :private-members: + :special-members: + + +csv +^^^ + +.. automodule:: pygeoapi.formatter.csv_ + :show-inheritance: + :members: + :private-members: + :special-members: + + +Process package +--------------- + +.. automodule:: pygeoapi.process + :show-inheritance: + :members: + :private-members: + :special-members: + +Base class +^^^^^^^^^^ + +.. automodule:: pygeoapi.process.base + :show-inheritance: + :members: + :private-members: + :special-members: + +hello_world +^^^^^^^^^^^ + +Hello world example process + +.. automodule:: pygeoapi.process.hello_world + :show-inheritance: + :members: + :private-members: + :special-members: + +.. _data Provider: + +Provider +-------- + + + +.. automodule:: pygeoapi.provider + :show-inheritance: + :members: + :private-members: + :special-members: + + + +Base class +^^^^^^^^^^ + +.. automodule:: pygeoapi.provider.base + :show-inheritance: + :members: + :private-members: + :special-members: + + +CSV provider +^^^^^^^^^^^^ + +.. automodule:: pygeoapi.provider.csv_ + :show-inheritance: + :members: + :private-members: + +Elastic provider +^^^^^^^^^^^^^^^^ + +.. automodule:: pygeoapi.provider.elasticsearch_ + :show-inheritance: + :members: + :private-members: + + +GeoJSON +^^^^^^^ + +.. automodule:: pygeoapi.provider.geojson + :show-inheritance: + :members: + :private-members: + + +GeoPackage +^^^^^^^^^^ + +.. automodule:: pygeoapi.provider.geopackage + :show-inheritance: + :members: + :private-members: + + +OGR +^^^ + +.. automodule:: pygeoapi.provider.ogr + :show-inheritance: + :members: + :private-members: + + +postgresql +^^^^^^^^^^ + +.. automodule:: pygeoapi.provider.postgresql + :show-inheritance: + :members: + :private-members: + + +sqlite +^^^^^^ + +.. automodule:: pygeoapi.provider.sqlite + :show-inheritance: + :members: + :private-members: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..527b4f7 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# pygeoapi documentation build configuration file, created by +# sphinx-quickstart on Wed Jul 3 06:22:56 2019. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.doctest', + 'sphinx.ext.githubpages'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'pygeoapi' +copyright = '2019, pygeoapi team' +author = 'pygeoapi team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.6' +# The full version, including alpha/beta/rc tags. +release = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pygeoapidoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'pygeoapi.tex', 'pygeoapi Documentation', + 'pygeoapi team', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pygeoapi', 'pygeoapi Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'pygeoapi', 'pygeoapi Documentation', + author, 'pygeoapi', 'pygeoapi provides an API to geospatial data', + 'Miscellaneous'), +] diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst new file mode 100644 index 0000000..c459da9 --- /dev/null +++ b/docs/source/configuration.rst @@ -0,0 +1,22 @@ +.. _configuration: + +Configuration +============= + +pygeoapi uses a yaml file as configuration source and the file location is read from the ``PYGEOAPI_CONFIG`` env variable + +.. note:: + pygeoapi is under high development, and new configuration paramenters are constantely being added. For the lastest parameters + please consult the `pygeoapi-config.yml `_ file provided on github + + + +Using ``pygeoapi-config.yml`` as reference we will have the following sections: + + * `server` for server related configurations + * `logging` for logging configuration + * `metadata` server and content metadata (information used to populate multiple content) + * `datasets` data content offered by server (collections in WFS3.0) + + + \ No newline at end of file diff --git a/docs/source/docker.rst b/docs/source/docker.rst new file mode 100644 index 0000000..f4ec61e --- /dev/null +++ b/docs/source/docker.rst @@ -0,0 +1,111 @@ +.. _docker: + +Docker +====== + +Docker Images ``geopython/pygeoapi:latest`` and versions are `available on DockerHub `_ . + +Each Docker Image contains a default configuration ``default.config.yml`` +with the project's test data and WFS3 datasets. + +You can override this default config via Docker Volume mapping or by extending the Docker Image +and copying in your config. + +See an `example for the geoapi demo server `_ +for the latter method. + +``_ +Depending on your config you may need specific backends to be available. + +Running - Basics +---------------- + +By default this Image will start a pygeoapi Docker Container +using ``gunicorn`` on internal port 80. + +To run with default built-in config and data: + +.. code-block:: console + + docker run -p 5000:80 -it geopython/pygeoapi run + # or simply + docker run -p 5000:80 -it geopython/pygeoapi + + +then browse to **http://localhost:5000** + +You can also run all unit tests to verify: + +.. code-block:: console + + docker run -it geopython/pygeoapi test + + +Running - Overriding the default config +--------------------------------------- + +Normally you would override the ``default.config.yml`` with your own ``pygeoapi`` config. +This can be effected best via Docker Volume Mapping. + +For example if your config is in ``my.config.yml``: + +.. code-block:: console + + docker run -p 5000:80 -v $(pwd)/my.config.yml:/pygeoapi/local.config.yml -it geopython/pygeoapi + + +But better/cleaner is to use ``docker-compose``. Something like: + +.. code-block:: yaml + + version: "3" + services: + pygeoapi: + image: geopython/pygeoapi:latest + volumes: + - ./my.config.yml:/pygeoapi/local.config.yml + +Or you can create a ``Dockerfile`` extending the base Image and **COPY** in your config: + + +.. code-block:: dockerfile + + FROM geopython/pygeoapi:latest + COPY ./my.config.yml /pygeoapi/local.config.yml + + +See how the demo server is setup (`here `_) + +Running - Running on a sub-path +------------------------------- + +By default the ``pygeoapi`` Docker Image will run from the ``root`` path ``/``. +If you need to run from a sub-path and have all internal URLs correct +you need to set ``SCRIPT_NAME`` environment variable. + +For example to run with ``my.config.yml`` on ``http://localhost:5000/mypygeoapi``: + +.. code-block:: console + + docker run -p 5000:80 -e SCRIPT_NAME='/mypygeoapi' -v $(pwd)/my.config.yml:/pygeoapi/local.config.yml -it geopython/pygeoapi + + +browse to **http://localhost:5000/mypygeoapi** + +Or within a ``docker-compose.yml`` full example: + +.. code-block:: yaml + + version: "3" + services: + pygeoapi: + image: geopython/pygeoapi:latest + volumes: + - ./my.config.yml:/pygeoapi/local.config.yml + ports: + - "5000:80" + environment: + - SCRIPT_NAME=/pygeoapi + + +See `pygeoapi demo service `_ for an full example. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..9450987 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,43 @@ +.. pygeoapi documentation master file, created by + sphinx-quickstart on Wed Jul 3 06:22:56 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to pygeoapi's documentation! +==================================== + +|build-status| |docs| |coverage| + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + pygeoapi + install + openapi + docker + wsgi + configuration + plugins + code + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + + +.. |build-status| image:: https://api.travis-ci.org/geopython/pygeoapi.svg?branch=master + :alt: build status + :target: https://travis-ci.org/geopython/pygeoapi + +.. |docs| image:: https://readthedocs.org/projects/pygeoapi/badge/?version=latest&style=plastic + :alt: Documentation Status + :target: https://readthedocs.org/projects/pygeoapi + +.. |coverage| image:: https://codecov.io/gh/geopython/pygeoapi/branch/master/graph/badge.svg + :alt: Test coverage + :target: https://codecov.io/gh/pygeoapi/pygeoapi.org diff --git a/docs/source/install.rst b/docs/source/install.rst new file mode 100644 index 0000000..40ef175 --- /dev/null +++ b/docs/source/install.rst @@ -0,0 +1,54 @@ +.. _install: + +Install +======= + +pygeoapi is nativally run as a Flask app (the code struct is an API and Flask is used as a wrapper). + +pygeoapi uses two configuration files: **pygeoapi-config.yml** and **openapi.yml**. First configuration contains all the information and setup to run pygeoapi while the second is exclusivally for the openapi. + +pygeoapi requires setting ``PYGEOAPI_CONFIG`` and ``PYGEOAPI_OPENAPI`` env variable. ``PYGEOAPI_CONFIG`` points to the yaml file containing the configuration, in the example +bellow we copy the ``local.config.yml`` default configuration to ``pygeoapi-config.yml`` and use this configuration file. + +``PYGEOAPI_OPENAPI`` variable is the path to openapi file configuration, this file **needs to be autogenerated** using the ``pygeoapi generate-openapi-document`` command and +the pygeoapi config files e.g: ``pygeoapi generate-openapi-document -c local.config.yml > openapi.yml``. And then setting the env variable to the path: +``export PYGEOAPI_OPENAPI=/path/to/openapi.yml`` + +For production environments it is recommended to use :ref:`docker` or a specialized WSGI HTTP server :ref:`wsgi` + +Copy/paste install +------------------ + +It is advisable to run pygeoapi inside an isolated enviroment either *virtualenv* or *docker*, mainly to avoid python package conflicts. + +.. code-block:: console + + #create virtualenv + virtualenv -p python pygeoapi + cd pygeoapi + . bin/activate + git clone https://github.com/geopython/pygeoapi.git + cd pygeoapi + + #install requirements + pip install -r requirements.txt + pip install -r requirements-dev.txt + + # install source in current directory + pip install -e . + cp pygeoapi-config.yml local.config.yml + #edit configuration file + nano local.config.yml + + export PYGEOAPI_CONFIG=/path/to/local.config.yml + # generate OpenAPI Document + pygeoapi generate-openapi-document -c local.config.yml > openapi.yml + export PYGEOAPI_OPENAPI=/path/to/openapi.yml + + #Run pygeoapi + pygeoapi serve + + +If the default configuration was used then we should have pygeoapi running on + +.. image:: /_static/openapi_intro_page.png diff --git a/docs/source/openapi.rst b/docs/source/openapi.rst new file mode 100644 index 0000000..59259fb --- /dev/null +++ b/docs/source/openapi.rst @@ -0,0 +1,64 @@ +.. _openapi: + +OpenAPI +======= + + +`OpenAPI spec `_ is an open specification for REST end points, currentely OGC services are being redefined using such specification. +The REST structure and payload are defined using yaml file structures, the file structure is described here: ``_ + +pygeoapi REST end points descriptions on OpenAPI standard are automatically generated based on the configuration file: + + +.. code-block:: console + + pygeoapi generate-openapi-document -c local.config.yml > openapi.yml + + +The api will them be accessible at `/api` endpoint. + +For api demo please check: ``_ + +The api page has REST description but also integrated clients that can be used to send requests to the REST end points and see the response provided + + +Using OpenAPI +------------- + +Acessing the openAPI webpage we have the following structure: + +.. image:: /_static/openapi_intro_page.png + +Please notice that **each dataset** will be represented as a REST end point under `collections` + + +In this example we will test and `GET` data concerning windmills in the Netherlands, first we will check the avaiable datasets, +by accessing the service's collections: + + +.. image:: /_static/openapi_get_collections.png + +The service collection metadata will contain a description of the collections provided by the server + +.. image:: /_static/openapi_get_collections_result.png + +The dataset `dutch_windmills` will be available on the `collections` end point, in the following example we'll obtain the specific metadata of the dataset + +.. image:: /_static/openapi_get_collection.png + +.. image:: /_static/openapi_get_collection_result.png + + +features/items composing the data are agregated on the `/items` end point, in this REST end point it is possible to obtain all dataset, or restrict +it features/items to a **numerical limit**, **bounding box**, **time stamp**, **pagging** (start index) + +.. image:: /_static/openapi_get_item.png + +For each feature in dataset we have a **specific identifier** (notice that the identifier is not part of the JSON properties), + +.. image:: /_static/openapi_get_item_id.png + +This identifier can be used to obtain a specific item from the dataset using the `items\{id}` end point as follows: + +.. image:: /_static/openapi_get_item_id2.png + diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst new file mode 100644 index 0000000..1d08eff --- /dev/null +++ b/docs/source/plugins.rst @@ -0,0 +1,63 @@ +.. _plugins: + +Plugins +======= + +In this section we will explain how pygeoapi uses a plugin approach for data providers, formatters and processes. + +Plugin data provider plugin +--------------------------- + +Plugins are in general modules containing derived classed classes that ensure minimal requirements for the plugin to work. +Lets consider the steps for a data provider plugin (source code is located here: :ref:`data Provider`) + +#. create a new module file on the `provider folder` (e.g myprovider.py) +#. copy code from `base.py` +#. import base provider class + + .. code-block:: python + + from pygeoapi.provider.base import BaseProvider + +#. create a child class from the `BaseProvider` class with a specific name + + .. code-block:: python + + class BaseProvider(object): + """generic Provider ABC""" + + def __init__(self, provider_def): + """ + Initialize object + + to become: + + .. code-block:: python + + class MyDataProvider(object): + """My data provider""" + + def __init__(self, provider_def): + """Inherit from parent class""" + BaseProvider.__init__(self, provider_def) + + + +#. implement class methods. + + .. code-block:: python + + def query(self): + + def get(self, identifier): + + def create(self, new_feature): + + def update(self, identifier, new_feature): + + def delete(self, identifier): + + +The above class methods are related to the specific URLs defined on the OGC openapi specification: + + diff --git a/docs/source/pygeoapi.rst b/docs/source/pygeoapi.rst new file mode 100644 index 0000000..6214851 --- /dev/null +++ b/docs/source/pygeoapi.rst @@ -0,0 +1,16 @@ +.. _pygeoapi: + +pygeoapi project +================ + +pygeoapi is a Python server implementation of the emerging suite of OGC API standards. pygeoapi is development +using the `12-factor app `_ approach, allowing for easy deployment in cloud systems. + +pygeoapi is a OsGEO recognized project (`here `_). + +Demo server +----------- + +There is a demo server on ``_ running the latest (Docker) version from the master branch of this repo. pygeoapi runs there at `https://demo.pygeoapi.io/master `_ . + +The demo server setup and config is maintained within a seperate GH repo: ``_. \ No newline at end of file diff --git a/docs/source/wsgi.rst b/docs/source/wsgi.rst new file mode 100644 index 0000000..f582956 --- /dev/null +++ b/docs/source/wsgi.rst @@ -0,0 +1,48 @@ +.. _wsgi: + +WSGI +==== + + Web Server Gateway Interface (WSGI) is standard for forwarding request to web applications written on Python language. pygeoapi it self + doesn't implement WSGI since it is an API, + therefore it is required a webframework to access HTTP requests and pass the information to pygeopai + +.. code-block:: console + + HTTP request --> Flask (flask_app.py) --> pygeopai API + + +the pygeoapi package integrates `Flask `_ as webframework for defining the API routes/end points and WSGI support. + +The flask WSGI server can be easely run as a pygeoapi command: + +.. code-block:: console + + pygeoapi serve + +Running a native Flask server is not adivsable, the prefered option is as follows: + +.. code-block:: console + + HTTP request --> WSGI server (gunicorn) --> Flask (flask_app.py) --> pygeopai API + +By having a specific WSGI server, the HTTPS are efficiently processed into threads/processes. The current docker pygeoapi +implement such strategy (see section: :ref:`docker`), it is prefered to implement pygeopai using docker solutions than running host native WSGI servers. + + +Running gunicorn +---------------- + +Gunicorn is one of several WSGI supporting server on python (list of server supporting WSGI: `here `_). This server +is simple to run from the command, e.g: + +.. code-block:: console + + gunicorn pygeoapi.flask_app:APP + +For extra configuration parameters like port binding, workers please consult the gunicorn `settings `_ + + + + + \ No newline at end of file diff --git a/pygeoapi/api.py b/pygeoapi/api.py index 1a679c8..0bbb1ea 100644 --- a/pygeoapi/api.py +++ b/pygeoapi/api.py @@ -26,6 +26,9 @@ # OTHER DEALINGS IN THE SOFTWARE. # # ================================================================= +""" Root level code of pygeoapi, parsing content provided by webframework. +Returns content from plugins and sets reponses +""" from datetime import datetime import json @@ -45,11 +48,13 @@ LOGGER = logging.getLogger(__name__) TEMPLATES = '{}{}templates'.format(os.path.dirname( os.path.realpath(__file__)), os.sep) +#: Return headers for requests (e.g:X-Powered-By) HEADERS = { 'Content-Type': 'application/json', 'X-Powered-By': 'pygeoapi {}'.format(__version__) } +#: Formats allowed for ?f= requests FORMATS = ['json', 'html'] @@ -833,9 +838,9 @@ def check_format(args, headers): def to_json(dict_): """ - serialize dict to json + Serialize dict to json - :param dict_: dict_ + :param dict_: `dict` of JSON representation :returns: JSON string representation """ diff --git a/pygeoapi/flask_app.py b/pygeoapi/flask_app.py index ed508c7..71192ad 100644 --- a/pygeoapi/flask_app.py +++ b/pygeoapi/flask_app.py @@ -28,6 +28,8 @@ # # ================================================================= +""" Flask module providing the route paths to the api""" + import os import click @@ -61,6 +63,11 @@ api_ = API(CONFIG) @APP.route('/') def root(): + """ + HTTP root content of pygeopai. Intro page access point + + :returns: HTTP response + """ headers, status_code, content = api_.root(request.headers, request.args) response = make_response(content, status_code) @@ -72,6 +79,11 @@ def root(): @APP.route('/api') def api(): + """ + OpenAPI access point + + :returns: HTTP response + """ with open(os.environ.get('PYGEOAPI_OPENAPI')) as ff: openapi = yaml_load(ff) @@ -87,6 +99,12 @@ def api(): @APP.route('/conformance') def api_conformance(): + """ + OGC open api conformance access point + + :returns: HTTP response + """ + headers, status_code, content = api_.api_conformance(request.headers, request.args) @@ -100,6 +118,13 @@ def api_conformance(): @APP.route('/collections') @APP.route('/collections/') def describe_collections(name=None): + """ + OGC open api collections access point + + :param name: identifier of collection name + :returns: HTTP response + """ + headers, status_code, content = api_.describe_collections( request.headers, request.args, name) @@ -113,6 +138,12 @@ def describe_collections(name=None): @APP.route('/collections//items') @APP.route('/collections//items/') def dataset(feature_collection, feature=None): + """ + OGC open api collections/{dataset}/items/{feature} access point + + :returns: HTTP response + """ + if feature is None: headers, status_code, content = api_.get_features( request.headers, request.args, feature_collection) @@ -131,6 +162,12 @@ def dataset(feature_collection, feature=None): @APP.route('/processes') @APP.route('/processes/') def describe_processes(name=None): + """ + OGC open api processes access point (experimental) + + :param name: identifier of process to describe + :returns: HTTP response + """ headers, status_code, content = api_.describe_processes( request.headers, request.args, name) @@ -144,6 +181,13 @@ def describe_processes(name=None): @APP.route('/processes//jobs', methods=['GET', 'POST']) def execute_process(name=None): + """ + OGC open api jobs from processes access point (experimental) + + :param name: identifier of process to execute + :returns: HTTP response + """ + if request.method == 'GET': headers, status_code, content = ({}, 200, "[]") elif request.method == 'POST': @@ -162,7 +206,14 @@ def execute_process(name=None): @click.pass_context @click.option('--debug', '-d', default=False, is_flag=True, help='debug') def serve(ctx, debug=False): - """Serve pygeoapi via Flask""" + """ + Serve pygeoapi via Flask. Runs pygeoapi + as a flask server. Not recommend for production. + + :param debug: `bool` of whether to run in debug mode + :returns void + + """ # setup_logger(CONFIG['logging']) APP.run(debug=True, host=api_.config['server']['bind']['host'], diff --git a/pygeoapi/formatter/__init__.py b/pygeoapi/formatter/__init__.py index 71c632c..9d1e649 100644 --- a/pygeoapi/formatter/__init__.py +++ b/pygeoapi/formatter/__init__.py @@ -26,3 +26,5 @@ # OTHER DEALINGS IN THE SOFTWARE. # # ================================================================= + +"""Output formatter package""" diff --git a/pygeoapi/formatter/csv_.py b/pygeoapi/formatter/csv_.py index 375fe72..7332264 100644 --- a/pygeoapi/formatter/csv_.py +++ b/pygeoapi/formatter/csv_.py @@ -46,7 +46,7 @@ class CSVFormatter(BaseFormatter): :param formatter_def: formatter definition - :returns: pygeoapi.formatter.csv_.CSVFormatter + :returns: `pygeoapi.formatter.csv_.CSVFormatter` """ geom = False diff --git a/pygeoapi/log.py b/pygeoapi/log.py index d3a134f..b8e9912 100644 --- a/pygeoapi/log.py +++ b/pygeoapi/log.py @@ -27,6 +27,8 @@ # # ================================================================= +"""Logging system""" + import logging import sys diff --git a/pygeoapi/plugin.py b/pygeoapi/plugin.py index 7ca39b2..24e9c87 100644 --- a/pygeoapi/plugin.py +++ b/pygeoapi/plugin.py @@ -26,12 +26,15 @@ # OTHER DEALINGS IN THE SOFTWARE. # # ================================================================= +"""Plugin loader""" import importlib import logging LOGGER = logging.getLogger(__name__) +#: Loads provider plugins to be used by pygeoapi,\ +#: formatters and processes available PLUGINS = { 'provider': { 'CSV': 'pygeoapi.provider.csv_.CSVProvider', diff --git a/pygeoapi/process/__init__.py b/pygeoapi/process/__init__.py index 71c632c..5aa5832 100644 --- a/pygeoapi/process/__init__.py +++ b/pygeoapi/process/__init__.py @@ -26,3 +26,5 @@ # OTHER DEALINGS IN THE SOFTWARE. # # ================================================================= + +"""OGC process package, each process is an independent module""" diff --git a/pygeoapi/process/base.py b/pygeoapi/process/base.py index 9fcd055..5ef613d 100644 --- a/pygeoapi/process/base.py +++ b/pygeoapi/process/base.py @@ -33,7 +33,7 @@ LOGGER = logging.getLogger(__name__) class BaseProcessor(object): - """generic Processor ABC""" + """generic Processor ABC. Processes are inherited from this class""" def __init__(self, processor_def, process_metadata): """ diff --git a/pygeoapi/process/hello_world.py b/pygeoapi/process/hello_world.py index 688d29c..07437f3 100644 --- a/pygeoapi/process/hello_world.py +++ b/pygeoapi/process/hello_world.py @@ -33,6 +33,7 @@ from pygeoapi.process.base import BaseProcessor LOGGER = logging.getLogger(__name__) +#: Process metadata and description PROCESS_METADATA = { 'version': '0.1.0', 'id': 'hello-world', @@ -80,7 +81,7 @@ PROCESS_METADATA = { class HelloWorldProcessor(BaseProcessor): - """Hello World Processor""" + """Hello World Processor example""" def __init__(self, provider_def): """ diff --git a/pygeoapi/provider/__init__.py b/pygeoapi/provider/__init__.py index 71c632c..7595c47 100644 --- a/pygeoapi/provider/__init__.py +++ b/pygeoapi/provider/__init__.py @@ -26,3 +26,5 @@ # OTHER DEALINGS IN THE SOFTWARE. # # ================================================================= + +"""Provider module containing the plugins wrapping data sources""" diff --git a/pygeoapi/provider/geojson.py b/pygeoapi/provider/geojson.py index e59ad80..fc2ca16 100644 --- a/pygeoapi/provider/geojson.py +++ b/pygeoapi/provider/geojson.py @@ -52,14 +52,14 @@ class GeoJSONProvider(BaseProvider): and will override any 'id' provided in the original data. The feature 'properties' will be preserved. - TODO - - query method should take bbox - - instead of methods returning FeatureCollections, - we should be yielding Features and aggregating in the view - - there are strict id semantics; all features in the input GeoJSON file - must be present and be unique strings. Otherwise it will break. - - How to raise errors in the provider implementation such that - appropriate HTTP responses will be raised + TODO: + * query method should take bbox + * instead of methods returning FeatureCollections, + we should be yielding Features and aggregating in the view + * there are strict id semantics; all features in the input GeoJSON file + must be present and be unique strings. Otherwise it will break. + * How to raise errors in the provider implementation such that + * appropriate HTTP responses will be raised """ def __init__(self, provider_def): @@ -73,6 +73,7 @@ class GeoJSONProvider(BaseProvider): Yes loading from disk, deserializing and validation happens on every request. This is not efficient. """ + if os.path.exists(self.data): with open(self.data) as src: data = json.loads(src.read()) @@ -104,6 +105,7 @@ class GeoJSONProvider(BaseProvider): :returns: FeatureCollection dict of 0..n GeoJSON features """ + # TODO filter by bbox without resorting to third-party libs data = self._load() @@ -124,6 +126,7 @@ class GeoJSONProvider(BaseProvider): :param identifier: feature id :returns: dict of single GeoJSON feature """ + all_data = self._load() for feature in all_data['features']: if str(feature['properties'][self.id_field]) == identifier: @@ -154,6 +157,7 @@ class GeoJSONProvider(BaseProvider): :param identifier: feature id :param new_feature: new GeoJSON feature dictionary """ + all_data = self._load() for i, feature in enumerate(all_data['features']): if feature['properties']['id'] == identifier: @@ -170,6 +174,7 @@ class GeoJSONProvider(BaseProvider): :param identifier: feature id """ + all_data = self._load() for i, feature in enumerate(all_data['features']): if feature['properties']['id'] == identifier: diff --git a/pygeoapi/util.py b/pygeoapi/util.py index 9613d60..bb1caf1 100644 --- a/pygeoapi/util.py +++ b/pygeoapi/util.py @@ -27,6 +27,8 @@ # # ================================================================= +"""Generic util functions used in the code""" + import logging import yaml diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..0be72a1 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,21 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/source/conf.py + + +# Optionally build your docs in additional formats such as PDF and ePub +formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: requirements.txt + - requirements: requirements-dev.txt diff --git a/requirements-dev.txt b/requirements-dev.txt index 63d7f52..84dec02 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,3 +20,6 @@ pypandoc pyOpenSSL ndg-httpsclient pyasn1 + +#Read the docs +sphinx_rtd_theme