1de0439e2d
* new file: docker/Dockerfile Dockerfile for pygeoapi * modified: pygeoapi/provider/__init__.py new file: tests/data/ne_110m_lakes.sqlite new file: tests/json_marshmallow.py new file: tests/test_sqlite_provider.py Sqlit implementation and testing marshmallows * new file: provider/sqlite.py new file: provider/tmp_parser.py Sqlite provider * Testing sqlalchemy * query for sqlite3 * Countries dataset, message in assert * yml config * table in data link and query implemented without limit * PR of refactor * functional sqlite3 driver * flake8 * pipreq for complete list of requirements * updated readme with working examples, extra requirements * typos, SQLite removed Dockerfile and ne_100m_lakes.sqlite * update requirements * pypandoc in requirements-dev.txt
203 lines
6.8 KiB
Python
203 lines
6.8 KiB
Python
# =================================================================
|
|
#
|
|
# Authors: Jorge Samuel Mendes de Jesus <jorge.dejesus@geocat.net>
|
|
#
|
|
# Copyright (c) 2018 Jorge Samuel Mendes de Jesus
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person
|
|
# obtaining a copy of this software and associated documentation
|
|
# files (the "Software"), to deal in the Software without
|
|
# restriction, including without limitation the rights to use,
|
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following
|
|
# conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
# OTHER DEALINGS IN THE SOFTWARE.
|
|
#
|
|
#
|
|
|
|
import sqlite3
|
|
import logging
|
|
import os
|
|
import geojson
|
|
from pygeoapi.provider.base import BaseProvider
|
|
from pygeoapi.provider import InvalidProviderError
|
|
|
|
LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
class SQLiteProvider(object):
|
|
"""Generic provide for SQLITE using sqlite3 module.
|
|
This module requires install of libsqlite3-mod-spatialite
|
|
TODO: DELETE, UPDATE, CREATE
|
|
"""
|
|
|
|
def __init__(self, provider_def):
|
|
"""
|
|
SQLiteProvider Class constructor
|
|
|
|
:param privider_def: provider definitions from yml pygeoapi-config.
|
|
data,id_field, name set in parent class
|
|
|
|
:returns: pygeoapi.providers.base.SQLiteProvider
|
|
"""
|
|
BaseProvider.__init__(self, provider_def)
|
|
|
|
self.table = provider_def['table']
|
|
|
|
self.dataDB = None
|
|
|
|
LOGGER.debug('Setting Sqlite propreties:')
|
|
LOGGER.debug('Data source:{}'.format(self.data))
|
|
LOGGER.debug('Name:{}'.format(self.name))
|
|
LOGGER.debug('ID_field:{}'.format(self.id_field))
|
|
LOGGER.debug('Table:{}'.format(self.table))
|
|
|
|
def __response_feature_collection(self):
|
|
"""Assembles GeoJSON output from DB query
|
|
|
|
:returns: GeoJSON FeaturesCollection
|
|
"""
|
|
|
|
feature_list = list()
|
|
for row_data in self.dataDB:
|
|
row_data = dict(row_data) # sqlite3.Row is doesnt support pop
|
|
geom = geojson.loads(row_data['AsGeoJSON(geometry)'])
|
|
del row_data['AsGeoJSON(geometry)']
|
|
feature = geojson.Feature(geometry=geom, properties=row_data)
|
|
feature_list.append(feature)
|
|
|
|
feature_collection = geojson.FeatureCollection(feature_list)
|
|
|
|
return feature_collection
|
|
|
|
def __response_feature_hits(self, hits):
|
|
"""Assembles GeoJSON/Feature number
|
|
|
|
:returns: GeoJSON FeaturesCollection
|
|
"""
|
|
|
|
feature_collection = geojson.FeatureCollection([])
|
|
feature_collection['numberMatched'] = str(hits)
|
|
return feature_collection
|
|
|
|
def __load(self):
|
|
"""
|
|
Private method for loading spatiallite,
|
|
get the table structure and dump geometry
|
|
|
|
:returns: sqlite3.Cursor
|
|
"""
|
|
|
|
if (os.path.exists(self.data)):
|
|
conn = sqlite3.connect(self.data)
|
|
else:
|
|
raise InvalidProviderError
|
|
|
|
conn.row_factory = sqlite3.Row
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT load_extension('mod_spatialite')")
|
|
cursor.execute("PRAGMA table_info({})".format(self.table))
|
|
|
|
result = cursor.fetchall()
|
|
try:
|
|
# TODO: Better exceptions declaring
|
|
# InvalidProviderError as Parent class
|
|
assert len(result), "Table not found"
|
|
assert len([item for item in result
|
|
if item['pk'] == 1]), "Primary key not found"
|
|
assert len([item for item in result
|
|
if self.id_field in item]), "id_field not present"
|
|
assert len([item for item in result
|
|
if 'GEOMETRY' in item]), "GEOMETRY column not found"
|
|
|
|
except InvalidProviderError:
|
|
raise
|
|
|
|
self.columns = [item[1] for item in result if item[1] != 'GEOMETRY']
|
|
self.columns = ",".join(self.columns)+",AsGeoJSON(geometry)"
|
|
|
|
return cursor
|
|
|
|
def query(self, startindex=0, limit=10, resulttype='results'):
|
|
"""
|
|
Query Sqlite for all the content.
|
|
e,g: http://localhost:5000/collections/countries/items?
|
|
limit=1&resulttype=results
|
|
|
|
:param startindex: starting record to return (default 0)
|
|
:param limit: number of records to return (default 10)
|
|
:param resulttype: return results or hit limit (default results)
|
|
|
|
:returns: GeoJSON FeaturesCollection
|
|
"""
|
|
LOGGER.debug('Querying Sqlite')
|
|
|
|
cursor = self.__load()
|
|
|
|
LOGGER.debug('Got cursor from DB')
|
|
|
|
if resulttype == 'hits':
|
|
res = cursor.execute("select count(*) as hits from {};".format(
|
|
self.table))
|
|
|
|
hits = res.fetchone()["hits"]
|
|
return self.__response_feature_hits(hits)
|
|
|
|
end_index = startindex + limit
|
|
# Not working
|
|
# http://localhost:5000/collections/countries/items/?startindex=10
|
|
sql_query = "select {} from {} where rowid >= ? \
|
|
and rowid <= ?;".format(self.columns, self.table)
|
|
|
|
LOGGER.debug('SQL Query:{}'.format(sql_query))
|
|
LOGGER.debug('Start Index:{}'.format(startindex))
|
|
LOGGER.debug('End Index'.format(end_index))
|
|
|
|
self.dataDB = cursor.execute(sql_query, (startindex, end_index, ))
|
|
|
|
feature_collection = self.__response_feature_collection()
|
|
return feature_collection
|
|
|
|
def get(self, identifier):
|
|
"""
|
|
Query the provider for a specific
|
|
feature id e.g: /collections/countries/items/1
|
|
|
|
:param identifier: feature id
|
|
|
|
:returns: GeoJSON FeaturesCollection
|
|
"""
|
|
|
|
LOGGER.debug('Get item from Sqlite')
|
|
|
|
cursor = self.__load()
|
|
|
|
LOGGER.debug('Got cursor from DB')
|
|
|
|
sql_query = "select {} from {} where {}==?;".format(self.columns,
|
|
self.table,
|
|
self.id_field)
|
|
|
|
LOGGER.debug('SQL Query:{}'.format(sql_query))
|
|
LOGGER.debug('Identifier:{}'.format(identifier))
|
|
|
|
self.dataDB = cursor.execute(sql_query, (identifier, ))
|
|
|
|
feature_collection = self.__response_feature_collection()
|
|
return feature_collection
|
|
|
|
def __repr__(self):
|
|
return '<SQliteProvider> {},{}'.format(self.data, self.table)
|